Exploring ViewModel Internals
At Google I/O 2017, Google introduced Architecture Components, a collection of libraries designed to address core challenges in Android development and provide guidance for app architecture. These components, part of the broader Android Jetpack library suite, include tools like Jetpack ViewModel to simplify state management and lifecycle awareness.
Jetpack ViewModel has long been a popular topic of discussion among developers, often serving as a cornerstone for implementing MV* design patterns, such as MVVM or MVI. It is frequently compared to Microsoft’s original MVVM pattern, highlighting its versatility and adaptation for Android app architecture.
Some developers might prefer implementing ViewModels manually, but Jetpack ViewModel remains a great role in many common scenarios due to its seamless integration with various libraries. It works effectively with Hilt for dependency injection into ViewModel constructors, with Jetpack Navigation for scoping ViewModel lifecycles, and with Jetpack Compose for exposing screen UI states to composable functions, making it a cornerstone of modern Android app architecture.
In this article, you’ll explore ViewModel's inner workings, diving into its internal implementation and understanding how it operates under the hood featured in Dove Letter. Dove Letter is a subscription repository where you can learn, discuss, and share new insights about Android and Kotlin with industrial Android developer interview questions, tips with code, articles, discussion, and trending news. If you’re interested in joining, be sure to check out “Learn Kotlin and Android With Dove Letter.”
ViewModel and ViewModelImpl
Let’s start by examining the ViewModel
class. ViewModel
is an abstract class that serves as a base for other classes, offering several key functions to manage UI-related data in a lifecycle-conscious way. Its internal implementation (JVM, Android) is outlined in the following code:
You might have noticed that the ViewModel
class contains a property of type ViewModelImpl
, and most of its functions simply delegate their calls to the corresponding functions in ViewModelImpl
. To fully understand how these functions operate, we should examine the internal workings of the ViewModelImpl
class:
Let’s break it down one by one. The ViewModelImpl
class contains the following key properties and functions:
isCleared
: This property holds the state indicating whether theViewModel
has already been cleared. Many function calls depend on this property, as it governs whether certain operations are permissible.closeables
andkeyToCloseables
: These properties manageAutoCloseable
instances.closeables
is a set, whilekeyToCloseables
is a map, allowing for efficient storage and retrieval of closeable resources.addCloseable()
: There are two variations of this function, but their primary purpose is the same — to add anAutoCloseable
instance to either the map or set. This is done in a thread-safe manner using aSynchronizedObject
, ensuring proper coordination in multi-threaded environments.clear()
: This function is responsible for closing all storedAutoCloseable
instances from bothcloseables
andkeyToCloseables
. It also resets thecloseables
set, ensuring no lingering resources are left behind.
AutoCloseable
Now you might be wondering about the exact purpose of AutoCloseable
. Originating from Java, AutoCloseable
is a functional interface designed to handle resource management. In Kotlin, AutoCloseable
works similarly and allows you to create an instance that executes a specified closeAction
when its close()
function is called as you’ve seen in the code below:
By now, you should have a clearer understanding of AutoCloseable
and its role in resource management. Essentially, the ViewModel
maintains a map and a set of AutoCloseable
instances, which can be added directly to the ViewModel
using the addCloseable()
function. When the ViewModel
is cleared, all stored AutoCloseable
instances are systematically closed by invoking their close()
function, ensuring proper cleanup of resources.
So what can you achieve with AutoCloseable
? While its use cases may not be extensive, it is particularly useful for delegating tasks that must be executed in the ViewModel
's onCleared()
function. For instance, when working with RxJava or RxKotlin, you need to call dispose()
on a CompositeDisposable
to clean up all Disposable
objects when the ViewModel
is cleared.
By utilizing AutoCloseable
, you can create a custom class or function to manage CompositeDisposable
, ensuring that all disposables are automatically disposed of when the ViewModel
is cleared. This approach simplifies resource cleanup and integrates seamlessly with the ViewModel
lifecycle.
Ensure that you are using version 2.8.5 or later of the Jetpack Lifecycle library.
At first glance, this approach might seem like over-engineering, as it adds more code compared to manually disposing of the CompositeDisposable
. However, consider a scenario where you have 100+ ViewModel
classes—this method not only simplifies resource management but also eliminates the risk of forgetting to call the dispose()
function in the onCleared()
method, ensuring consistent and reliable cleanup across your codebase.
viewModelScope
One of the most common examples of leveraging AutoCloseable
is the viewModelScope
, which is frequently used in daily development to launch coroutine scopes within a ViewModel
. Let’s take a look at the internal implementation of viewModelScope
:
Examining the internal implementation of viewModelScope
, it first checks if a ViewModel
scope has already been created; if it exists, it retrieves the existing scope. If not, it creates a new ViewModelScope
using the createViewModelScope()
function. To fully understand this process, it's essential to delve into what the createViewModelScope()
function does.
The createViewModelScope()
function returns a coroutine scope called CloseableCoroutineScope
, which implements AutoCloseable
. This scope ensures that the coroutine context is canceled when the close()
function is called.
Internally, the mechanism works as follows: viewModelScope
creates a custom coroutine scope that is automatically canceled by leveraging the AutoCloseable
interface. This scope is then registered with the ViewModel
by adding it through the addCloseable()
function, ensuring seamless integration with the ViewModel
lifecycle.
Conclusion
In this article, you’ve explored the internal mechanisms of Jetpack ViewModel
, gaining a deeper understanding of how it operates, particularly the implementation and functionality of viewModelScope
. While the complete ViewModel
mechanism is more intricate—encompassing dedicated providers and lifecycle scoping tailored to Android components—the insights shared here should be sufficient for most project requirements. Hopefully, this article has clarified the workings of ViewModel
and provided valuable insights into the internal behavior of viewModelScope
.
This topic initially has been covered in Dove Letter, a private repository offering daily insights on Android and Kotlin, including topics like Compose, architecture, industry interview questions, and practical code tips. In just 20 weeks since its launch, Dove Letter has surpassed 400 individual subscribers and 12business/lifetime subscribers. If you’re eager to deepen your knowledge of Android, Kotlin, and Compose, be sure to check out ‘Learn Kotlin and Android With Dove Letter’.