Blog Infos
Author
Published
Topics
, , , ,
Author
Published

This will be #4 in the series “Mastering Android ViewModels”, We’ve discussed tips for improving performance and code quality in the ViewModels which are bare-bones Android applications nowadays

We’ve so far discussed In the previous parts 🔄🔄🔄

  1. Avoid initializing the state in the init {} block. ✅ (read here)
  2. Avoid exposing mutable states ✅ (read here)
  3. Use update{} when using MutableStateFlows ✅ (read here)
  4. Try not to import Android dependencies in the ViewModels  (read here)
  5. Lazily inject dependencies in the constructor ✅ (read here)

Find links to the previous parts at the end of this article!

And if we look at our whole list:
  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.

We can get started with #5,#6 and #7 for today!

Let’s do this

#5-Embrace more reactive and less imperative coding

Imagine you are working on a search feature in your Android app. You want to pass the query that the user types using the keyboard to an API and display the results. One way of doing this is through the traditional imperative approach, where you manually fetch and update the search results. However, a more modern and efficient way is to embrace reactive programming with Kotlin Flow in your Android ViewModel. Let’s start by looking at the imperative approach:

Imperative approach:

In the imperative approach, you typically call methods to fetch and update the search results directly. Let’s look at an example:

class SearchViewModel‌ @Inject constructor(private val searchRepo: SearchRepository) : ViewModel() {
val searchResults: StateFlow<List<SearchResult>>
field = MutableStateFlow<List<SearchResult>>()
fun search(query: String) {
viewModelScope.launch {
val results = searchRepository.search(query)
searchResults.update { results }
}
}
}

While this approach works, it is less efficient, especially when dealing with frequent updates such as real-time search queries. Each key press triggers a new search, which can be resource-intensive and lead to an unresponsive UI. Also harder to understand and requires more cognitive load if we are working with more of reactive approach in different parts of the project which we most probably are as we are working with Flows and LiveDatas nowadays.

imperative approach is harder to understand and requires more cognitive load if we are working with more of reactive approach in different parts of the project which we most probably are

Now let’s explore the reactive approach:

Reactive Approach:
class SearchViewModel @Inject constructor(private val searchRepo: SearchRepository): ViewModel() {
private val _searchQuery = MutableStateFlow("")
val searchResults: StateFlow<List<SearchResult>> = _searchQuery
.debounce(300) // Add a debounce to limit requests
.filter(String::isNotEmpty) // Ignore empty queries
.flatMapLatest(searchRepository::search)
.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
fun setSearchQuery(query: String) {
_searchQuery.update { query }
}
}
Benefits of the Reactive Approach
  1. Efficiency: Debouncing and filtering reduce unnecessary API calls, improving performance and user experience.
  2. Responsiveness: The UI updates automatically in response to changes in the search query, providing a smooth and interactive user experience.
  3. Maintainability: The declarative nature of the reactive approach makes the code cleaner and easier to maintain. It separates the concerns of state management and UI updates.

Switching from an imperative to a reactive approach in Android ViewModel using Kotlin Flow offers significant benefits, particularly for features like search where real-time responsiveness is crucial. By leveraging Flow, you can create more efficient, responsive, and maintainable applications.

Embrace the power of reactive programming to build modern Android apps that provide a seamless user experience.

#6-Avoid initializing the ViewModel from the outside world:
#7-Avoid passing parameters from the outside world:

One common anti-pattern that I have seen in many code bases is initializing the ViewModel from the outside world. This practice can lead to various issues, including unexpected results and unnecessary API calls due to lifecycle changes.

such as within the onViewCreated method of a Fragment or the onCreate method of an Activity. Sometimes because we need to pass a parameter to initiate the viewModel or without knowing this might cause issues.

Consider the following scenario: You need to fetch user data in your ViewModel. A typical but problematic approach is to call an initialization method like fetchUserData() directly within the Fragment’s onViewCreated method or in onCreate() method of the Activity

// In a Fragment
class UserFragment : Fragment() {
private val userViewModel: UserViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val userId = "12345" // Get this from arguments or somewhere else
userViewModel.fetchUserData(userId) // Called from Fragment
}
}

Job Offers

Job Offers


    Senior Android Developer

    SumUp
    Berlin
    • Full Time
    apply now

    Senior Android Engineer

    Carly Solutions GmbH
    Munich
    • Full Time
    apply now

OUR VIDEO RECOMMENDATION

Why is adaptive layout a nightmare?

Aujourd’hui il est indéniable que le développement d’une application mobile est nécessaire pour toute entreprise qui souhaite rester concurrentielle. La mise en place d’applications rassemble de nombreux intérêts : nouveau canal d’acquisition, accessibilité et engagement…
Watch Video

Why is adaptive layout a nightmare?

Jobs

So what is really wrong with this approach? Lot’s of things, this is wrong to many aspects:

Lifecycle Issues
  • Redundant API Calls: Every time the Fragment or Activity is recreated (e.g., during configuration changes like screen rotations), the fetchUserData method is called again, leading to unnecessary and excessive API calls. In order to test this you can add some logs to the logcat and execute the following command to see what will really happen:
adb shell content insert --uri content://settings/system --bind name:s:font_scale --bind value:f:1.15;adb shell content insert --uri content://settings/system --bind name:s:font_scale --bind value:f:1.0
Script to trigger multiple configuration changes
  • Inconsistent State: Multiple API calls can result in inconsistent states if the data changes between calls, and it might not be clear which data is the latest.
  • Missed Initialization: If the initialization method is not called due to a lifecycle event (e.g., the Fragment is recreated before the call happens), the ViewModel may end up in an uninitialized state.
2. Tight Coupling
  • Fragment-or-Activity-ViewModel Dependency: The ViewModel becomes tightly coupled with the Fragment or Activity, making it difficult to reuse the ViewModel in other parts of the application.
  • Increased Complexity: The Fragment or Activity needs to manage not only UI logic but also initialization logic for the ViewModel, increasing complexity.
3. Responsibility Violation
  • Single Responsibility Principle: The Fragment should handle UI-related logic and only observe the emitted data, while the ViewModel should handle business logic and data management. Mixing these responsibilities violates this principle.
4. Error-Prone
  • Manual Method Calls: Reliance on manually calling methods like fetchUserData increases the risk of human error, such as forgetting to call the method or passing incorrect parameters.
  • Lifecycle Timing Issues: Calling initialization methods from the Fragment or Activity increases the likelihood of lifecycle timing issues, where the method may be called at the wrong time in the lifecycle.
5. Testing Difficulties
  • Difficult to Unit Test: Unit testing the ViewModel becomes more challenging because its initialization depends on the Fragment’s lifecycle and state.
  • Mocking Required: Properly mocking the Fragment’s lifecycle and interactions with the ViewModel is required for testing, adding complexity.
6. Resource Wastage
  • Network and Server Resources: Redundant API calls due to configuration changes waste network bandwidth and server resources, impacting both user experience and server load.
7. Increased Maintenance
  • Code Maintainability: Having initialization logic scattered between the Fragment and ViewModel makes the code harder to maintain and understand.
  • Code Duplication: Similar initialization logic might be duplicated across multiple Fragments or Activities, leading to more code to maintain and higher chances of bugs.
  • Technical debt: If we decide to move away from fragments and go fully compose, a technical debt is left here hanging to cover
8. Performance Issues
  • UI Freezes: If the API calls are made synchronously or block the main thread, it can lead to UI freezes and a poor user experience. extra effort is needed to move the work to a background thread compared to not initializing the work from outside ViewModel
  • Inefficient Resource Usage: Continuous re-fetching of data can lead to inefficient use of device resources, such as battery and CPU.

We can talk about other possible downsides of using this approach but let’s discuss what could be done otherwise ✨

Recommended Approach ✨

To avoid these issues, the ViewModel should handle its own initialization internally and don’t rely on outside ViewModel initialization, We can do this by combination of using the first point in the first part of this series at:

https://proandroiddev.com/mastering-android-viewmodels-essential-dos-and-donts-part-1-%EF%B8%8F-bdf05287bca9?source=post_page—–a0bad53cebd2——————————–

And by using SavedStateHandle from AndroidX’s lifecycle library, Take a look at the following documents from Google:

https://developer.android.com/topic/libraries/architecture/viewmodel/viewmodel-savedstate?source=post_page—–a0bad53cebd2——————————–

So by using SavedStateHandle and getting the arguments we need from the whatever navigation library we’re using, it’d be much efficient to initialize the ViewModel in cases we need an external parameter from the outside and if we don’t need any external parameters by using the #1 point discussed in the first series we can avoid any manual initialization in total!

Conclusion:

Mastering the use of ViewModels in Android development is crucial for creating robust, efficient, and maintainable applications. Throughout this series, we’ve discussed a comprehensive set of best practices designed to improve your code quality and application performance.

🌟 Congratulations if you’ve made it this far in the article! 🎉 Don’t forget to:

  • 👏 smash the clap button as many times! So I can continue with the follow-up articles!
  • Follow my YouTube channel for video tutorials and tips on Android development
  • ✨✨ If you need help with your Android ViewModels, Project or your career development, Book a 1:1 or a Pair-Programming session with me, Book a time now 🧑‍💻🧑‍💻🧑‍💻
  • check out the previous articles in this series with the links below:

https://proandroiddev.com/mastering-android-viewmodels-essential-dos-and-donts-part-1-%EF%B8%8F-bdf05287bca9?source=post_page—–a0bad53cebd2——————————–

https://proandroiddev.com/mastering-android-viewmodels-essential-dos-and-donts-part-2-%EF%B8%8F-2b49281f0029?source=post_page—–a0bad53cebd2——————————–

https://proandroiddev.com/mastering-android-viewmodels-essential-dos-and-donts-part-3-%EF%B8%8F3%EF%B8%8F%E2%83%A3-1833ce3ddd2b?source=post_page—–a0bad53cebd2——————————–

This article is previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
Using annotations in Kotlin has some nuances that are useful to know
READ MORE
blog
One of the latest trends in UI design is blurring the background content behind the foreground elements. This creates a sense of depth, transparency, and focus,…
READ MORE
blog
Now that Android Studio Iguana is out and stable, I wanted to write about…
READ MORE
blog
The suspension capability is the most essential feature upon which all other Kotlin Coroutines…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu