Memory Leaks with Fragments in Android

PROBLEM

  • It’s own lifecycle (onCreate and onDestroy)
  • It’s view’s lifecycle (onCreateView and onDestroyView)

EXAMPLE

class ExampleFragment : Fragment { // Both the view and adapter will leak memory
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: ExampleAdapter

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.example_fragment, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
adapter = ExampleAdapter()

recyclerView = view.findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.adapter = adapter
}
}

ATTEMPTED SOLUTION

class ListViewBindingMemoryLeakFragment : Fragment {

private var binding: ListViewBindingMemoryLeakFragmentBinding? = null
private var adapter: ListViewBindingMemoryLeakAdapter? = null

// A non-null reference to the binding or adapter will leak memory
// private lateinit var binding: ListViewBindingMemoryLeakFragmentBinding
// private lateinit var adapter: ListViewBindingMemoryLeakAdapter

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = ListViewBindingMemoryLeakFragmentBinding.inflate(LayoutInflater.from(requireContext()), null, false)
return binding?.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
adapter = ListViewBindingMemoryLeakAdapter()
binding?.recyclerView?.adapter = adapter
}

override fun onDestroyView() {
super.onDestroyView()
binding = null
adapter = null
}
}

FINAL SOLUTION

fun <T : ViewBinding> Fragment.viewBinding(
viewBindingFactory: (View) -> T
): ReadOnlyProperty<Fragment, T> = object : ReadOnlyProperty<Fragment, T> {

private var binding: T? = null

init {
viewLifecycleOwnerLiveData.observe(this@viewBinding, Observer { viewLifecycleOwner ->
viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) {
(binding as? ViewDataBinding)?.unbind()
binding = null
}
})
})

lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
view ?: error("You must either pass in the layout ID into ${this@viewBinding.javaClass.simpleName}'s constructor or inflate a view in onCreateView()")
}
})
}

override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
binding?.let { return it }

val viewLifecycleOwner = try {
thisRef.viewLifecycleOwner
} catch (e: IllegalStateException) {
error("Should not attempt to get bindings when Fragment views haven't been created yet. The fragment has not called onCreateView() at this point.")
}
if (!viewLifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
error("Should not attempt to get bindings when Fragment views are destroyed. The fragment has already called onDestroyView() at this point.")
}

return viewBindingFactory(thisRef.requireView()).also { viewBinding ->
if (viewBinding is ViewDataBinding) {
viewBinding.lifecycleOwner = viewLifecycleOwner
}

this.binding = viewBinding
}
}
}

USAGE OF SOLUTION IN FRAGMENT

// Must pass layout id to constructor of fragment or override onCreateView()
class ListViewBindingDelegateSolutionFragment : Fragment(R.layout.list_view_binding_delegate_solution_fragment) {

// Init view binding delegate. It will automatically
// null out the ViewBinding object internally.
private val binding by viewBinding(ListViewBindingDelegateSolutionFragmentBinding::bind)

// Adapter is also tied to view lifecycle so we cannot hold onto
// a strong reference to it or it will also leak memory. Use a
// computed property to evaluate getting the adapter every
// time you need it.
private val adapter get() = binding.exampleRecyclerView.adapter as ListViewBindingDelegateSolutionAdapter

// Must consume binding after it has been created, thus we must use
// onViewCreated rather than onCreateView or else we crash
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.exampleRecyclerView.adapter = ListViewBindingDelegateSolutionAdapter()
binding.exampleTextView.text = "Hello world!"

viewModel.exampleItemsLiveData.observe(viewLifecycleOwner) { items ->
adapter.setItems(items)
}
}
}

DRAWBACKS

SAMPLE PROJECT

SUMMARY

REFERENCES

--

--

--

Senior Android Developer at Procore Technologies.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

How to Create a Splash Screen

MoviesPreview: the Android app repository architecture

How to array of maps (data in firestore) showing firestore recycler view on android studio (Auto…

Android InboxStyle Notification As Deep As Possible

Android push notification not working when the app is closed

Why Should You Choose Flutter ?

very bullish on BAT: Brave adds Gemini support to Android nightly!

Native Android Way to Implement Scoping in Hilt

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Max Kohne

Max Kohne

Senior Android Developer at Procore Technologies.

More from Medium

RecycleView pull to refresh using kotlin

Find the CRUD operations in Room Database in Attendance Tracker Android app (Kotlin) — Part 3

Why Jetpack data store?

Retrofit with ViewModel in Kotlin (Part-1)