Developing modern applications for the platform Android requires strict adherence to architectural patterns to ensure that the code remains maintainable and the user interface is responsive. At the center of this ecosystem is a component ViewModelthat has become an integral part of the library Android Architecture Components. Many novice developers are wondering: android, what is a viewmodel and why is it difficult to imagine a quality application today without it?
This class is designed to store and manage data related to the user interface, taking into account the life cycle of an activity or fragment. The main feature is that the data inside the ViewModel survives configuration changes, such as screen rotation or system theme changes. This avoids loss of application state and the need to reload data from the network or database every time the device is rotated.
Using ViewModel in conjunction with LiveData or StateFlow implements a reactive approach to programming. When data changes within the logical layer, the interface is automatically updated with the new values. This approach divides responsibility: the Activity or Fragment deals only with displaying information and processing user actions, while the ViewModel takes care of all the business logic and state management.
The main task and principle of operation of the component
The key role ViewModel is the division of responsibility between the user interface and the business logic of the application. Previously, developers often stored data directly in the Activity, which led to information loss when the component was recreated. Now the ViewModel acts as a reliable storage that lasts longer than the visual part of the application. It provides data for the UI and processes user actions without knowing the specific details of the interface implementation.
The life cycle ViewModel is closely related to the life cycle of the owner, which is usually an Activity or Fragment. However, unlike them, the ViewModel is not destroyed when configuration changes occur. It persists until the owner finally completes his work, for example when the user closes the application or the system kills the process to free up resources. This makes ViewModel an ideal place to store temporary session data.
The interaction between the UI and ViewModel is based on observed data. You should not pass the Activity context inside the ViewModel as this creates the risk of memory leaks. Instead, special wrappers are used, such as LiveData or streams StateFlowthat allow the UI to subscribe to changes. As soon as the data is updated, the subscriber receives a notification and redraws the interface.
โ ๏ธ Warning: Never pass
Context,VieworActivityobjects inside the ViewModel constructor. This will lead to a memory leak, since the ViewModel can live longer than the Activity by holding a reference to an already destroyed interface.
The ViewModel exists as long as its owner's scope is active, surviving screen rotations and other configuration changes without losing data.
Integration with MVVM and LiveData architecture
The most common use case ViewModel is architecture MVVM (Model-View-ViewModel). In this design, the View (Activity or Fragment) is only responsible for displaying data and forwarding user events. The ViewModel acts as an intermediary that requests data from the Model (repositories, databases, APIs) and prepares it for display. This separation simplifies testing of application logic, since the ViewModel does not depend on Android platform components.
The class LiveDatais most often used to transfer data from ViewModel to View. It is an observable data holder that respects the lifecycle of components. If the Activity is in state STOPPED, LiveData it will not send updates, which prevents unnecessary work and possible errors. When the activity returns to the active state (STARTED or RESUMED), the latest current set of data will be delivered to the observer automatically.
The modern development stack also actively uses coroutines (Kotlin Coroutines) inside the ViewModel to perform asynchronous tasks. You can run network queries or database operations on a background thread using viewModelScope. This scope automatically cancels all running coroutines when the ViewModel is cleared, which saves the developer from manually canceling tasks and preventing leaks.
- ๐ฆ Separation of responsibilities: UI handles display, ViewModel handles logic and data.
- ๐ Reactivity: Automatic update of the interface when data changes via LiveData or StateFlow.
- ๐ก๏ธ Security: Protection against memory leaks due to the absence of direct references to the View.
- โก Asynchrony: Built-in coroutine support via viewModelScope for background tasks.
Creating and initializing a ViewModel in a project
To get started with ViewModel you need to add the appropriate dependencies to the file build.gradle your module. Typically this is a library androidx.lifecycle:lifecycle-viewmodel-ktxthat provides Kotlin extensions for convenient work. After connecting the dependencies, you can create your own class, inheriting from ViewModel, and implement the necessary logic in it.
To get a ViewModel instance inside an Activity or Fragment, you cannot use a regular constructor new. Instead, you should use a special factory class ViewModelProvider. It ensures that only one ViewModel instance is created for a given owner, and configuration changes will return the same object with the data saved.
val viewModel: MyViewModel by viewModels()
The above code uses a property delegate viewModels()which is syntactic sugar over ViewModelProvider. This is the cleanest and recommended way of initialization in modern Kotlin projects. If you need to pass arguments to the ViewModel constructor (for example, the ID of the element to load), you need to implement your own ViewModelProvider.Factory, which will be responsible for creating an instance with the necessary parameters.
How to pass parameters to the ViewModel?
To pass arguments you need to create a class that implements the ViewModelProvider.Factory interface. In the create() method, you check the type of the requested ViewModel and return a new instance with the arguments passed to the factory. This factory then needs to be passed to the ViewModelProvider.
State management and event processing
One โโof the most common problems when working with ViewModel is the difference between State and Events. Status is data that should be displayed on the screen at all times, such as a list of products or error text. LiveData or StateFlowis ideal for this. Events such as navigating to another screen or showing Snackbarshould be processed only once.
If you try to use normal LiveData for navigation, then when you rotate the screen or return to the application, the event may fire again, since LiveData stores the last value. To avoid this, special wrappers are used, such as SingleLiveEvent or streams Channel / SharedFlow with customization onBufferOverflow = BufferOverflow.DROP_OLDEST. This ensures that the event is only consumed by one observer once.
It is also important to manage data loading correctly. A typical pattern includes a loading state variable (isLoading), which changes to true before the request begins and to false after it completes. The UI observes this flag and shows or hides the progress indicator (ProgressBar). This creates a feeling of responsiveness of the application for the user.
โ ๏ธ Attention: Do not store references to View elements (buttons, text fields) in the ViewModel. The ViewModel must be independent of the Android platform so that it can be easily tested on the JVM without an emulator.
| Component | Purpose | Lifecycle | Use example |
|---|---|---|---|
| ViewModel | Storing and managing UI data | Until owner completion (Activity/Fragment) | Contact list, user profile |
| LiveData | Observable data container | Active only when owner is active | Displaying error text, counter |
| Repository | Single data source | Independent of UI (singleton) | API requests, working with Room DB |
| SavedStateHandle | Saving data when killing a process | Related to the saved state of the system | Object ID, search parameters |
Saving data when killing a process
Although ViewModel copes well with configuration changes, it cannot save data if the Android system completely kills the application process to free up memory (for example, when the user minimizes the application and opens many other heavy apps). In this case, when you return to the application, the ViewModel will be re-created, and all data in it will be lost.
There is a mechanism SavedStateHandleto solve this problem. This is an object that can be injected into the ViewModel's constructor. It acts as a key-value map in which data is stored by the system when the Activity state is saved. Even if the process is killed and restored, the values โโin SavedStateHandle will remain intact.
Use SavedStateHandle requires minimal effort. You simply declare it in the ViewModel constructor and use the set() and get() or observable getLiveData() methods to work with the data. This is especially critical for parameters such as the ID of the element being loaded, the current list page, or the text of the search query.
Use SavedStateHandle to store primitive data types and identifiers, but do not try to store huge lists of objects or image bitmaps there - this will slow down state saving.
Common mistakes and best practices
Despite the simplicity of the concept, developers often make mistakes during implementation ViewModel. One of the most common is trying to perform heavy operations, such as network requests or complex image processing, directly in ViewModel initialization blocks. This can block the thread and cause the interface to hang. All such operations must be wrapped in coroutines and run asynchronously.
Another mistake is creating multiple ViewModel instances for one screen. This leads to data desynchronization: the user enters text in one place, but the ViewModel in another place does not know about it. Always make sure you use the same owner scope (eg Activity) to obtain the ViewModel instance in all associated Fragments.
You should also avoid turning the ViewModel into a "God Object" - a class that knows too much and does too much. If your ViewModel has grown to thousands of lines of code, it may be worth separating some of the logic into separate_use_cases_ or interactors. The ViewModel should remain a thin layer that coordinates the data flow between the UI and the domain layer.
- ๐ซ You cannot: Store the Activity or View context inside the ViewModel.
- โ You must: Use SavedStateHandle for critical data when killing a process.
- ๐ซ You must not: Perform blocking operations on the main thread inside the ViewModel.
- โ Need: Break large logic into separate UseCase classes for code purity.
โ ๏ธ Attention: Android Jetpack library interfaces and methods of working with ViewModel may be updated. Always check the official Android Developers documentation for information on the latest versions of libraries and recommended patterns.
โ๏ธ Checking the ViewModel implementation
Frequently asked questions (FAQ)
What is the main difference between ViewModel and Presenter in the MVP pattern?
The main difference is awareness of the life cycle and the presence of an interface. The Presenter in an MVP often knows about the View through the interface and controls it directly. The ViewModel doesn't know about the View at all, it simply provides data through observable streams. In addition, the ViewModel automatically survives configuration changes, while the Presenter needs to be recreated and rebinded manually.
Can ViewModel be used in regular Java classes without an Activity?
No, ViewModel is designed to work in the context of Android lifecycle components (Activity, Fragment, Service). For pure platform-independent business logic, you should use regular classes (Use Cases, Interactors) that can be called from the ViewModel. The ViewModel itself is part of the presentation layer in a broader sense, but is isolated from the UI.
What happens if I instantiate a ViewModel via a new MyViewModel()?
If you instantiate via a constructor, you lose all the benefits of the architecture. When you rotate the screen, this object will be destroyed along with the Activity, the data will be lost, and a new object will be created from scratch. Additionally, you will not be able to use dependency injection and SavedStateHandle as provided by the ViewModelProvider factory.
How to pass data from one ViewModel to another?
Directly passing data between ViewModels is not recommended, since they should not know about each other. The best way is to use a shared data source (Repository) or Shared ViewModel if we are talking about related Fragments within one Activity. For the global state of the application, you can use a single object (Singleton) or a Dependency Injection container.