[Android Kotlin]: ViewModel Overview

[Fuente: https://developer.android.com/topic/libraries/architecture/viewmodel]

The ViewModel class is a business logic or screen level state holder. It exposes state to the UI and encapsulates related business logic. Its principal advantage is that it caches state and persists it through configuration changes. This means that your UI doesn’t have to fetch data again when navigating between activities, or following configuration changes, such as when rotating the screen.Objective: This guide explains the basics of ViewModels, how they fit into Modern Android Development, and how you can implement them in your app.

For more information on state holders, see the state holders guidance. Similarly, for more information on the UI layer generally, see the UI layer guidance.

ViewModel benefits

The alternative to a ViewModel is a plain class that holds the data you display in your UI. This can become a problem when navigating between activities or Navigation destinations. Doing so destroys that data if you don’t store it using the saving instance state mechanism. ViewModel provides a convenient API for data persistence that resolves this issue.

The key benefits of the ViewModel class are essentially two:

  • It allows you to persist UI state.
  • It provides access to business logic.

Note: ViewModel fully supports integration with key Jetpack libraries such as Hilt and Navigation, as well as Compose.

Persistence

ViewModel allows persistence through both the state that a ViewModel holds, and the operations that a ViewModel triggers. This caching means that you don’t have to fetch data again through common configuration changes, such as a screen rotation.

Scope

When you instantiate a ViewModel, you pass it an object that implements the ViewModelStoreOwner interface. This may be a Navigation destination, Navigation graph, activity, fragment, or any other type that implements the interface. Your ViewModel is then scoped to the Lifecycle of the ViewModelStoreOwner. It remains in memory until its ViewModelStoreOwner goes away permanently.

A range of classes are either direct or indirect subclasses of the ViewModelStoreOwner interface. The direct subclasses are ComponentActivityFragment, and NavBackStackEntry. For a full list of indirect subclasses, see the ViewModelStoreOwner reference.

When the fragment or activity to which the ViewModel is scoped is destroyed, asynchronous work continues in the ViewModel that is scoped to it. This is the key to persistence.

For more information, see the section below on ViewModel lifecycle.

SavedStateHandle

SavedStateHandle allows you to persist data not just through configuration changes, but across process recreation. That is, it enables you to keep the UI state intact even when the user closes the app and opens it at a later time.

Access to business logic

Even though the vast majority of business logic is present in the data layer, the UI layer can also contain business logic. This can be the case when combining data from multiple repositories to create the screen UI state, or when a particular type of data doesn’t require a data layer.

ViewModel is the right place to handle business logic in the UI layer. The ViewModel is also in charge of handling events and delegating them to other layers of the hierarchy when business logic needs to be applied to modify application data.

Jetpack Compose

When using Jetpack Compose, ViewModel is the primary means of exposing screen UI state to your composables. In a hybrid app, activities and fragments simply host your composable functions. This is a shift from past approaches, where it wasn’t that simple and intuitive to create reusable pieces of UI with activities and fragments, which caused them to be much more active as UI controllers.

The most important thing to keep in mind when using ViewModel with Compose is that you cannot scope a ViewModel to a composable. This is because a composable is not a ViewModelStoreOwner. Two instances of the same composable in the Composition, or two different composables accessing the same ViewModel type under the same ViewModelStoreOwner would receive the same instance of the ViewModel, which often is not the expected behavior.

To get the benefits of ViewModel in Compose, host each screen in a Fragment or Activity, or use Compose Navigation and use ViewModels in composable functions as close as possible to the Navigation destination. That is because you can scope a ViewModel to Navigation destinations, Navigation graphs, Activities, and Fragments.

For more information, see the guide on state hoisting for Jetpack Compose.

Implement a ViewModel

The following is an example implementation of a ViewModel for a screen that allows the user to roll dice.Important: In this example, the responsibility of acquiring and holding the list of users sits with the ViewModel, not an Activity or Fragment directly.

data class DiceUiState(
    val firstDieValue: Int? = null,
    val secondDieValue: Int? = null,
    val numberOfRolls: Int = 0,
)

class DiceRollViewModel : ViewModel() {

    // Expose screen UI state
    private val _uiState = MutableStateFlow(DiceUiState())
    val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()

    // Handle business logic
    fun rollDice() {
        _uiState.update { currentState ->
            currentState.copy(
                firstDieValue = Random.nextInt(from = 1, until = 7),
                secondDieValue = Random.nextInt(from = 1, until = 7),
                numberOfRolls = currentState.numberOfRolls + 1,
            )
        }
    }
}

You can then access the ViewModel from an activity as follows:

import androidx.activity.viewModels

class DiceRollActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same DiceRollViewModel instance created by the first activity.

        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val viewModel: DiceRollViewModel by viewModels()
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Update UI elements
                }
            }
        }
    }
}

Caution: ViewModel usually shouldn’t reference a view, Lifecycle, or any class that may hold a reference to the activity context. Because the ViewModel lifecycle is larger than the UI’s, holding a lifecycle-related API in the ViewModel could cause memory leaks.

Note: To import ViewModel into your Android project, see the instructions for declaring dependencies in the Lifecycle release notes.

Use coroutines with ViewModel

ViewModel includes support for Kotlin coroutines. It is able to persist asynchronous work in the same manner as it persists UI state.

For more information, see Use Kotlin coroutines with Android Architecture Components.

The lifecycle of a ViewModel

The lifecycle of a ViewModel is tied directly to its scope. A ViewModel remains in memory until the ViewModelStoreOwner to which it is scoped disappears. This may occur in the following contexts:

  • In the case of an activity, when it finishes.
  • In the case of a fragment, when it detaches.
  • In the case of a Navigation entry, when it’s removed from the back stack.

This makes ViewModels a great solution for storing data that survives configuration changes.

Figure 1 illustrates the various lifecycle states of an activity as it undergoes a rotation and then is finished. The illustration also shows the lifetime of the ViewModel next to the associated activity lifecycle. This particular diagram illustrates the states of an activity. The same basic states apply to the lifecycle of a fragment.

Illustrates the lifecycle of a ViewModel as an activity changes state.

You usually request a ViewModel the first time the system calls an activity object’s onCreate() method. The system may call onCreate() several times throughout the existence of an activity, such as when a device screen is rotated. The ViewModel exists from when you first request a ViewModel until the activity is finished and destroyed.

Clearing ViewModel dependencies

The ViewModel calls the onCleared method when the ViewModelStoreOwner destroys it in the course of its lifecycle. This allows you to clean up any work or dependencies that follows the ViewModel’s lifecycle.

The following example shows an alternative to viewModelScopeviewModelScope is a built-in CoroutineScope that automatically follows the ViewModel’s lifecycle. The ViewModel uses it to trigger business-related operations. If you want to use a custom scope instead of viewModelScope for easier testing, the ViewModel can receive a CoroutineScope as a dependency in its constructor. When the ViewModelStoreOwner clears the ViewModel at the end of its lifecycle, the ViewModel also cancels the CoroutineScope.

class MyViewModel(
    private val coroutineScope: CoroutineScope =
        CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
) : ViewModel() {

    // Other ViewModel logic ...

    override fun onCleared() {
        coroutineScope.cancel()
    }
}

From lifecycle version 2.5 and above, you can pass one or more Closeable objects to the ViewModel’s constructor that automatically closes when the ViewModel instance is cleared.

class CloseableCoroutineScope(
    context: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context
    override fun close() {
        coroutineContext.cancel()
   }
}

class MyViewModel(
    private val coroutineScope: CoroutineScope = CloseableCoroutineScope()
) : ViewModel(coroutineScope) {
    // Other ViewModel logic ...
}

Best practices

The following are several key best practices you should follow when implementing ViewModel:

  • Because of their scoping, use ViewModels as implementation details of a screen level state holder. Don’t use them as state holders of reusable UI components such as chip groups or forms. Otherwise, you’d get the same ViewModel instance in different usages of the same UI component under the same ViewModelStoreOwner.
  • ViewModels shouldn’t know about the UI implementation details. Keep the names of the methods the ViewModel API exposes and those of the UI state fields as generic as possible. In this way, your ViewModel can accommodate any type of UI: a mobile phone, foldable, tablet, or even a Chromebook!
  • As they can potentially live longer than the ViewModelStoreOwner, ViewModels shouldn’t hold any references of lifecycle-related APIs such as the Context or Resources to prevent memory leaks.
  • Don’t pass ViewModels to other classes, functions or other UI components. Because the platform manages them, you should keep them as close to it as you can. Close to your Activity, fragment, or screen level composable function. This prevents lower level components from accessing more data and logic than they need.

Further information

As your data grows more complex, you might choose to have a separate class just to load the data. The purpose of ViewModel is to encapsulate the data for a UI controller to let the data survive configuration changes. For information about how to load, persist, and manage data across configuration changes, see Saved UI States.

The Guide to Android App Architecture suggests building a repository class to handle these functions.

Additional resources

For further information about the ViewModel class, consult the following resources.

Documentation

ViewModel CheatSheet

Create ViewModels with dependencies  

Following dependency injection’s best practices, ViewModels can take dependencies as parameters in their constructor. These are mostly of types from the domain or data layers. Because the framework provides the ViewModels, a special mechanism is required to create instances of them. That mechanism is the ViewModelProvider.Factory interface. Only implementations of this interface can instantiate ViewModels in the right scope.

Note: If the ViewModel takes no dependencies or just the SavedStateHandle type as a dependency, you don’t need to provide a factory for the framework to instantiate instances of that ViewModel type.

Note: When injecting ViewModels using Hilt as a dependency injection solution, you don’t have to define a ViewModel factory manually. Hilt generates a factory that knows how to create all ViewModels annotated with @HiltViewModel for you at compile time. Classes annotated with @AndroidEntryPoint can directly access the Hilt generated factory when calling the regular ViewModel APIs.

ViewModels with CreationExtras

If a ViewModel class receives dependencies in its constructor, provide a factory that implements the ViewModelProvider.Factory interface. Override the create(Class<T>, CreationExtras) function to provide a new instance of the ViewModel.

CreationExtras allows you to access relevant information that helps instantiate a ViewModel. Here’s a list of keys that can be accessed from extras:

KeyFunctionality
ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEYProvides access to the custom key you passed to ViewModelProvider.get().
ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEYProvides access to the instance of the Application class.
SavedStateHandleSupport.DEFAULT_ARGS_KEYProvides access to the Bundle of arguments you should use to construct SavedStateHandle.
SavedStateHandleSupport.SAVED_STATE_REGISTRY_OWNER_KEYProvides access to the SavedStateRegistryOwner that is being used to construct the ViewModel.
SavedStateHandleSupport.VIEW_MODEL_STORE_OWNER_KEYProvides access to the ViewModelStoreOwner that is being used to construct the ViewModel.

To create a new instance of SavedStateHandle, use the CreationExtras.createSavedStateHandle().createSavedStateHandle()) function and pass it to the ViewModel.

The following is an example of how to provide an instance of a ViewModel that takes a repository scoped to the Application class and SavedStateHandle as dependencies:

    import androidx.lifecycle.SavedStateHandle
    import androidx.lifecycle.ViewModel
    import androidx.lifecycle.ViewModelProvider
    import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
    import androidx.lifecycle.createSavedStateHandle
    import androidx.lifecycle.viewmodel.CreationExtras

    class MyViewModel(
        private val myRepository: MyRepository,
        private val savedStateHandle: SavedStateHandle
    ) : ViewModel() {

        // ViewModel logic
        // ...

        // Define ViewModel factory in a companion object
        companion object {

            val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
                @Suppress("UNCHECKED_CAST")
                override fun <T : ViewModel> create(
                    modelClass: Class<T>,
                    extras: CreationExtras
                ): T {
                    // Get the Application object from extras
                    val application = checkNotNull(extras[APPLICATION_KEY])
                    // Create a SavedStateHandle for this ViewModel from extras
                    val savedStateHandle = extras.createSavedStateHandle()

                    return MyViewModel(
                        (application as MyApplication).myRepository,
                        savedStateHandle
                    ) as T
                }
            }
        }
    }

Note: It’s a good practice to place ViewModel factories in their ViewModel file for better context, readability, and easier discovery. The same ViewModel factory can be used for multiple ViewModels when they share dependencies, as it’s the case for the Architecture Blueprints sample.

Then, you can use this factory when retrieving an instance of the ViewModel:

import androidx.activity.viewModels

class MyActivity : AppCompatActivity() {

    private val viewModel: MyViewModel by viewModels { MyViewModel.Factory }

    // Rest of Activity code
}

Alternatively, use the ViewModel factory DSL to create factories using a more idiomatic Kotlin API:

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory

class MyViewModel(
    private val myRepository: MyRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    // ViewModel logic

    // Define ViewModel factory in a companion object
    companion object {
        val Factory: ViewModelProvider.Factory = viewModelFactory {
            initializer {
                val savedStateHandle = createSavedStateHandle()
                val myRepository = (this[APPLICATION_KEY] as MyApplication).myRepository
                MyViewModel(
                    myRepository = myRepository,
                    savedStateHandle = savedStateHandle
                )
            }
        }
    }
}

Factories for ViewModel version older than 2.5.0

If you’re using a version of ViewModel older than 2.5.0, you need to provide factories from a subset of classes that extend ViewModelProvider.Factory and implement the create(Class<T>) function. Depending on what dependencies the ViewModel needs, a different class needs to be extended from:

If Application or SavedStateHandle aren’t needed, simply extend from ViewModelProvider.Factory.

The following example uses an AbstractSavedStateViewModelFactory for a ViewModel that takes a repository and a SavedStateHandle type as a dependency:KotlinJava

class MyViewModel(
private val myRepository: MyRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {

// ViewModel logic ...

// Define ViewModel factory in a companion object
companion object {
    fun provideFactory(
        myRepository: MyRepository,
        owner: SavedStateRegistryOwner,
        defaultArgs: Bundle? = null,
    ): AbstractSavedStateViewModelFactory =
        object : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
            @Suppress("UNCHECKED_CAST")
            override fun <T : ViewModel> create(
                key: String,
                modelClass: Class<T>,
                handle: SavedStateHandle
            ): T {
                return MyViewModel(myRepository, handle) as T
            }
        }
    }
}

Warning: if you’re using a version of ViewModel older than 2.5.0, you might have the option to override a create function with CreationExtras in its signature. Don’t override that function. Instead, override the create function that has the key, modelClass and savedStateHandle parameters.

Then, you can use factory to retrieve your ViewModel:KotlinJavaJetpack Compose

import androidx.activity.viewModels

class MyActivity : AppCompatActivity() {

    private val viewModel: MyViewModel by viewModels {
        MyViewModel.provideFactory((application as MyApplication).myRepository, this)
    }

    // Rest of Activity code
}