Lifecycle-aware Lazy Property in Kotlin for Android Development

Lifecycle
from Android DeveloperIf you are developing Android apps using Kotlin, lazy property is one of the useful features that helps us save some memory by delaying the initialisation until they are requested and keeping that instance for the rest of the usage. However, recently I have encountered a nasty bug, which causes a memory-leak, while using it in fragment. In this article, I will show you the potential bug you might face with lazy property in activity/fragment and how to solve it.
What is the problem?
The bug happened when I was showing a bottom sheet in Fragment. In order to have the sheet collapse, expand and hidden appropriately, I needed to set the BottomSheetBehavior to the view that I wanted to use as a sheet in the layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"> <LinearLayout
android:id="@+id/bottomSheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/bottom_sheet_behavior"> .... </LinearLayout></androidx.coordinatorlayout.widget.CoordinatorLayout>
And then in the Fragment, I created the behavior by kotlin synthetic syntax and lazy property:
private val bottomSheetBehavior: BottomSheetBehavior<*>? by lazy {
BottomSheetBehavior.from(bottomSheet)
}
Everything worked fine until I tried the new navigation component in the app. Previously, I had Fragment A, hosted by Activity A, and Fragment B, hosted by Activity B. Therefore, moving between theses fragments only caused them to stop and start again without having their views destroyed. With the new navigation (this is still true for replacing fragments in an activity), the previous fragment instance is kept in the stack while its view is destroyed. This means that the bottomSheet
above still references to an old view when I go back and thus it causes a memory leak, which not only is bad for memory management in general but also introduce some weird UI behavior.
Lifecycle-aware Lazy to the rescue
Although there are many traditional ways to solve this, like using lateinit var
or a nullable var
to set a new value every time in, for example, onViewCreated
, I want to use a solution where the property can re-initialise itself based on the Fragment’s lifecycle (yes I’m lazy I know :D). That’s why I come up with the solution below:
Regarding the implementation, I have copied mostly from Kotlin and added lifecycle-related code there. Basically, we have a lazy property which is also a lifecycle observer. When it is first accessed, the internal value is initialised and the property start to observe the lifecycle. When the lifecycle is destroyed, the property release its current reference and stop listening to the lifecycle. If our property is requested again, the cycle repeats.
One important note: I disregard the synchronisation in this lifecycle-aware lazy property because it deals with view’s lifecycle directly and thus should only be used in main thread.
Hopefully this article help you find what you’re looking for. Happy coding :)