class MyFragment : Fragment() { private var someView: View? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.f_my_fragment, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) someView = view.findViewById(R.id.view) } }
What do you think? Is everything fine here? I'll give you a minute :)
Nope, there is a memory leak. Our someView variable will become a memory leak when fragment goes to back stack. Ideally we must remove all references to views in onDestroyView() method:
class MyFragment : Fragment() { private var someView: View? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.f_my_fragment, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) someView = view.findViewById(R.id.view) } override fun onDestroyView() { super.onDestroyView() someView = null } }
Now we have fixed this memory leak. But this process can become cumbersome when we deal with multiple views. Also it is pretty easy to forget to release all references.
But we can do better!
So I wrote this property delegate which automatically releases all references to views:
private class FragmentViewProperty<T : View> : ReadWriteProperty<Fragment, T>, LifecycleObserver { private var view: T? = null private var lifecycle: Lifecycle? = null override fun getValue(thisRef: Fragment, property: KProperty<*>): T { return view ?: throw NullPointerException() } override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) { view = value lifecycle?.removeObserver(this) lifecycle = thisRef.viewLifecycleOwner.lifecycle.also { it.addObserver(this) } } @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) private fun onViewDestroyed() { view = null lifecycle?.removeObserver(this) lifecycle = null } } fun <T: View> fragmentView(): ReadWriteProperty<Fragment, T> = FragmentViewProperty()
And now we can rewrite our code:
class MyFragment : Fragment() { private var someView by fragmentView<View>() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.f_my_fragment, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) someView = view.findViewById(R.id.view) someView.requestFocus() // as an added benefit we don't need to do null-checks now } }
Hope this was helpful. Cheers.