4

Mastering Android ViewModels: Essential Dos and Don’ts Part 1 🛠️

 1 month ago
source link: https://proandroiddev.com/mastering-android-viewmodels-essential-dos-and-donts-part-1-%EF%B8%8F-bdf05287bca9
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Mastering Android ViewModels: Essential Dos and Don’ts Part 1 🛠️

If you’re using ViewModels keep these in mind for better code quality

Published in
5 min readMar 17, 2024
1*kw5CfF3tMvC_SQaKneKuag.png

In this series of articles, we dive into best practices for utilizing Android ViewModels, emphasizing essential dos and don’ts to enhance code quality. We’ll cover the role of ViewModels in managing UI state and business logic, strategies for lazy dependency injection, and the importance of reactive programming. Additionally, we discuss common pitfalls to avoid, such as improper state initialization and exposing mutable states, providing a comprehensive guide for developers.

Understanding ViewModels

According to Android’s documentation, the ViewModel class acts as a business logic or screen-level state holder. It exposes the state to the UI and encapsulates related business logic. Its principal advantage is caching the state and persisting it through configuration changes. This means your UI doesn’t have to fetch data again when navigating between activities or following configuration changes, such as when rotating the screen.

Key Discussion Points for This Series

  1. Avoid initializing state in the init {} block.
  2. Avoid exposing mutable states.
  3. Use update{} when using MutableStateFlows:
  4. Lazily inject dependencies in the constructor.
  5. Embrace more reactive and less imperative coding.
  6. Avoid initializing the ViewModel from the outside world.
  7. Avoid passing parameters from the outside world.
  8. Avoid hardcoding Coroutine Dispatchers.
  9. Unit test your ViewModels.
  10. Avoid exposing suspended functions.
  11. Leverage the onCleared() callback in ViewModels.
  12. Handle process death and configuration changes.
  13. Inject UseCases, which call Repositories, which in turn call DataSources.
  14. Only include domain objects in your ViewModels.
  15. Leverage shareIn() and stateIn() operators to avoid hitting the upstream multiple times.

Let’s get started with the first item on the list!

#1-Avoid initializing state in the init {} block:

Initiating data loading in the init {} block of an Android ViewModel might seem convenient for initializing data as soon as the ViewModel is created. However, this approach has several downsides, such as tight coupling with ViewModel creation, testing challenges, limited flexibility, handling configuration changes, resource management, and UI responsiveness. To mitigate these issues, it's recommended to use a more deliberate approach to data loading, leveraging LiveData or other lifecycle-aware components to manage data in a way that respects the Android lifecycle.

Tight Coupling with ViewModel Creation:

Loading data in the init{} block couples data fetching tightly with the ViewModel’s lifecycle. This can lead to difficulties in controlling the timing of data loads, especially in complex UIs where you might want more granular control over when data is fetched based on user interactions or other events.

Testing Challenges:

Testing becomes harder because the data loading starts as soon as the ViewModel is instantiated. This can make it difficult to test the ViewModel in isolation without also triggering network requests or database queries, complicating test setup and potentially leading to flaky tests.

Limited Flexibility:

Automatically starting data loading upon ViewModel instantiation limits your flexibility in handling different user flows or UI states. For example, you might want to delay fetching data until certain user permissions are granted or until the user navigates to a specific part of your app.

Handling Configuration Changes:

Android ViewModels are designed to survive configuration changes, such as screen rotations. If data loading is initiated in the init{} block, a configuration change might lead to unexpected behavior or unnecessary data re-fetching if not carefully managed.

Resource Management:

Instant data loading might lead to inefficient use of resources, especially if the user does not need the data immediately upon entering the app or screen. This can be particularly problematic for applications that consume a significant amount of data or use costly operations to fetch or process this data.

UI Responsiveness:

Initiating data load in the init{} block can impact UI responsiveness, especially if the data load operation is lengthy or blocks the main thread. It’s generally a good practice to keep the init{} block lightweight and offload heavy or asynchronous operations to a background thread or use LiveData/Flow to observe data changes.

To mitigate these issues, it’s often recommended to use a more deliberate approach to data loading, such as triggering it in response to specific user actions or UI events and leveraging LiveData or other lifecycle-aware components to manage data in a way that respects the Android lifecycle. This can help ensure that your app remains responsive, is easier to test, and makes more efficient use of resources.

Let’s explore some examples of this anti-pattern:

Example #1:

Bad implementation of a ViewModel in Android

In this SearchViewModel, data loading is triggered immediately within the init block, tightly coupling data fetching to ViewModel instantiation and reducing flexibility. Exposing a mutable state _state inside the class and not handling potential errors or varying UI states (loading, success, error) can lead to a less robust and harder-to-test implementation. This approach undermines the benefits of ViewModel’s lifecycle awareness and the efficiency of lazy initialization.

How can we make it better?

Improve #1:

The refactoring removes data fetching from the ViewModel’s initblock, instead relying on collection to initiate data loading. This change significantly improves flexibility in managing data fetching and reduces unnecessary operations upon ViewModel instantiation, directly addressing issues of premature data loading and enhancing the ViewModel’s responsiveness and efficiency.

Example #2:

Launching a coroutine within the init block of SearchViewModel for immediate data processing ties data fetching too closely to the ViewModel’s lifecycle, potentially leading to inefficiencies and lifecycle management issues. This approach risks unnecessary network calls and complicates error handling, especially before the UI is ready to handle or display such information. Moreover, it assumes an implicit return to the main thread for UI updates, which may not always be safe or efficient, and it makes testing more challenging by initiating data fetching immediately upon ViewModel instantiation.

And we can refactor it to the following:

The revised implementation avoids launching a coroutine directly within the init block to observe searchQuery changes, instead opting for a reactive setup that transforms searchQuery into LiveData outside of the coroutine context. This eliminates potential issues related to lifecycle management and coroutine cancellation, ensuring that data fetching is inherently lifecycle-aware and more resource-efficient. By not relying on the init block to start observing and processing user input, it also decouples the ViewModel’s initialization from its data fetching logic, leading to a cleaner separation of concerns and a more maintainable code structure.

Summary:

We’ve delved into the reasons why initiating data loading within the init{} block can hinder our progress and have explored more intelligent, streamlined methods to orchestrate our app’s UI and logic via ViewModels. Throughout, we’ve discussed straightforward solutions and essential tactics to evade frequent pitfalls.

If you like this article please can you do me a little favour and hit the 👏clap button as many times! I really appreciate your kindness x❤️👊


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK