Understanding Android SavedState and SavedStateHandle
Understanding Android SavedState and SavedStateHandle
Building robust Android applications often requires careful handling of app states, especially when the lifecycle of activities or fragments comes into play. The Android platform offers several tools to manage state across configuration changes or process death, with two of the most common being SavedState
and SavedStateHandle
. In this blog post, we’ll take a closer look at these two concepts, explaining how they work and when you should use them.
What is SavedState?
In Android, SavedState
refers to the mechanism that allows you to preserve critical information when an activity or fragment is recreated. This typically happens during configuration changes such as screen rotation, or when the app is killed by the system and needs to be restored later.
Traditionally, Android developers relied on the onSaveInstanceState
method to store information in a Bundle
, and then used that Bundle
to restore the state in onCreate
or onViewStateRestored
. However, this approach can get cumbersome, especially when dealing with complex data or larger-scale applications.
Enter SavedStateHandle
The SavedStateHandle
is a more modern and convenient approach introduced as part of Android’s Jetpack components. It’s particularly useful when working with ViewModel
and Fragment
components, providing a seamless way to save and retrieve UI-related data even after process death.
How SavedStateHandle Works
SavedStateHandle
is essentially a key-value map that allows you to store small pieces of data that should survive configuration changes or process death. It works closely with the ViewModel
to ensure the state persists beyond the usual lifecycle of a fragment or activity. Instead of manually handling Bundle
objects, you can interact with SavedStateHandle
in a more straightforward way.
Under the hood, SavedStateHandle
relies on the SavedStateRegistry
mechanism provided by the Android framework. When a ViewModel
is created, it is given a reference to a SavedStateHandle
that is linked to the SavedStateRegistryOwner
(typically an activity or fragment). The SavedStateRegistry
manages the state by storing key-value pairs in a Bundle
when the lifecycle owner is stopped, and then restores those values when it is recreated. This ensures that the data is retained across configuration changes and even process death, making it a powerful tool for state management.
For example, consider the following usage in a ViewModel
:
class MyViewModel(
private val savedStateHandle: SavedStateHandle,
) : ViewModel() {
fun setUserName(name: String) {
savedStateHandle[KEY_USER_NAME] = name
}
companion object {
private const val KEY_USER_NAME = "user_name"
}
}
Here, SavedStateHandle
allows you to save the user name with a specific key, and retrieve it later, simplifying the process of managing UI state in a configuration-safe manner.
Benefits of Using SavedStateHandle
-
Integration with ViewModel: Unlike traditional
onSaveInstanceState
,SavedStateHandle
is integrated withViewModel
. This means it benefits from theViewModel
’s lifecycle awareness, making it easier to maintain state during configuration changes. -
Simplified Code: No more manual handling of
Bundles
and lifecycle methods. The API is intuitive, allowing you to focus on the logic rather than lifecycle intricacies. -
Automatic Process Death Handling:
SavedStateHandle
automatically manages saving and restoring data in cases of process death, which can be complex to handle manually.
When to Use SavedStateHandle
- Fragments with ViewModel: When you have a fragment that uses a
ViewModel
,SavedStateHandle
is ideal for preserving UI-related state between fragment recreations. - Configuration Changes: If you need to persist small amounts of data, like user input or filter criteria, during configuration changes,
SavedStateHandle
can simplify your implementation. - State Restoration After Process Death: If you want your application to restore its state seamlessly after the OS terminates your app to free up resources,
SavedStateHandle
can be your go-to tool.
Practical Example: Saving User Input
Let’s consider a fragment where a user types their name into a text field. You can easily manage this state with SavedStateHandle
and StateFlow
to ensure the data persists across configuration changes.
class MyViewModel(
private val savedStateHandle: SavedStateHandle,
) : ViewModel() {
private val _uiStateFlow = MutableStateFlow(
UiState(
userInput = savedStateHandle["user_input"] = input
)
)
val uiStateFlow = _uiStateFlow.asStateFlow()
fun saveUserInput(input: String) {
savedStateHandle["user_input"] = input
_uiStateFlow.update {
it.copy(userInput = input)
}
}
data class UiState(
val userInput: String? = null,
)
}
In your fragment, you would observe this data and update the UI accordingly:
class MyFragment : Fragment() {
private val viewModel: MyViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val userInputEditText = view.findViewById<EditText>(R.id.userInputEditText)
// Observe the saved input
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiStateFlow.collect { uiState ->
userInputEditText.setText(uiState.userInput ?: "")
}
}
}
}
}
With this approach, user input is automatically saved and restored, creating a smooth user experience without manually managing bundles and lifecycle intricacies.
Conclusion
SavedState
and SavedStateHandle
are essential tools for Android developers aiming to create more resilient and lifecycle-aware applications. While SavedState
using onSaveInstanceState
is still a valid approach, SavedStateHandle
provides a cleaner and more integrated solution, especially when working with Jetpack’s ViewModel
and Fragment
components. By leveraging these tools, you can ensure that your app handles configuration changes and process death smoothly, leading to a better user experience.