ViewModel vs Saved Instance State
While saved instance state stores an activity record across process death, it also stores an activity record across a configuration change. When you first launch the activity, the saved instance state bundle is null. When you rotate the device, the OS calls onSaveInstanceState(Bundle) on your activity. The OS then passes the data you stashed in the bundle to onCreate(Bundle?).
If saved instance state protects from both configuration changes and process death, why bother with a ViewModel at all? To be fair, GeoQuiz is so simple that you could have gotten away with saved instance state only.
However, most apps do not rely on a small, hardcoded question bank data set like GeoQuiz does. Most apps these days pull dynamic data from a database, from the internet, or from a combination of both. These operations are asynchronous, can be slow, and use precious battery and networking resources. And tying these operations to the activity lifecycle can be very tedious and error prone.
ViewModel really shines when you use it to orchestrate dynamic data for the activity, as you will see firsthand in Chapter 11 and Chapter 24. For example, ViewModel makes continuing a download operation across a configuration change simple. It also offers an easy way to keep data that was expensive to load in memory across a configuration change. And, as you have seen, ViewModel gets cleaned up automatically once the user finishes the activity.
ViewModel does not shine in the process death scenario since it gets wiped away from memory along with the process and everything in it. This is where saved instance state takes center stage. But saved instance state has its own limitations. Since saved instance state is serialized to disk, you should avoid stashing any large or complex objects.
As of this writing, the Android team is actively working to improve the developer experience using ViewModels. lifecycle-viewmodel-savedstate is a new library that was just released to allow ViewModels to save their state across process death. This should alleviate some of the difficulties of using ViewModels alongside saved instance state from your activities.
So it is not a matter of “which is better, ViewModel or saved instance state?” Savvy developers use saved instance state and ViewModel in harmony.
Use saved instance state to store the minimal amount of information necessary to re-create the UI state (for example, the current question index). Use ViewModel to cache the rich set of data needed to populate the UI in memory across configuration changes for quick and easy access. When the activity is re-created after process death, use the saved instance state information to set up the ViewModel as if the ViewModel and activity were never destroyed.
As of this writing, there is no easy way to determine whether an activity is being re-created after process death versus a configuration change. Why does this matter? A ViewModel stays in memory during a configuration change. So if you use the saved instance state data to update the ViewModel after the configuration change, you are making your app do unnecessary work. If the work causes the user to wait or uses their resources (like battery) unnecessarily, this redundant work is problematic.
One way to fix this problem is to make your ViewModel a little smarter. When setting a ViewModel value might result in more work, first check whether the data is fresh before doing the work to pull in and update the rest of the data:
class SomeFancyViewModel : ViewModel() { ... fun setCurrentIndex(index: Int) { if (index != currentIndex) { currentIndex = index // Load current question from database } } }
Neither ViewModel nor saved instance state is a solution for long-term storage. If your app needs to store data that should live on as long as the app is installed on the device, regardless of your activity’s state, use a persistent storage alternative. You will learn about two local persistent storage options in this book: databases, in Chapter 11, and shared preferences, in Chapter 26. Shared preferences is fine for very small, very simple data. A local database is a better option for larger, more complex data. In addition to local storage, you could store data on a remote server somewhere. You will learn how to access data from a web server in Chapter 24.
If GeoQuiz had many more questions, it could make sense to store the questions in a database or on a web server, rather than hardcoding them into the ViewModel. Since the questions are constant, it makes sense to persist them independent of activity lifecycle state. But accessing databases is a relatively slow operation, compared to accessing values in memory. So it makes sense to load what you need to display your UI and retain that in memory while the UI is showing using a ViewModel.
In this chapter, you squashed GeoQuiz’s state-loss bugs by correctly accounting for configuration changes and process death. In the next chapter, you will learn how to use Android Studio’s debugging tools to troubleshoot other, more app-specific bugs that might arise.