Some Best-Practices for Saving UI States in Android Development

Kayvan Kaseb
Software Development
8 min readApr 20, 2023
The picture is provided by Unsplash

As an Android developer, saving UI states is an essential aspect of developing robust and user-friendly apps. When an Android application’s UI state is not properly saved and restored, it can cause a poor user experience. This leads to user frustration and dissatisfaction. As a result, it plays a significant role for Android developers to understand and implement the best-practices for saving UI states in their Android apps efficiently. This article will discuss some best-practices and approaches for saving UI states in Android development.

Introduction and Overview

As a matter of fact, User Experience (UX) is a critical aspect of Android app development that requires close attention from developers. Users tend to favor apps that are simple to use, navigate, and have an intuitive design that caters to their needs. The ability to save and restore user interface (UI) states is a crucial component of an excellent user experience. This ensures that users do not lose any data or progress when they leave or close the app. In these situations the user expects the UI state to remain the same; however, the system destroys the activity and any state stored in it. Regardless of whether a user navigates away from the Android app or rotates their device, retaining the UI state not only enhances the user experience, but also increases the reliability and efficiency of your app in practice. The UI state is comprised of various user interface elements, such as the scroll bar position, text inputted in a text field, selected item in a list, and other relevant information. When a user leaves the app or rotates their device, this information may be lost, requiring the user to re-enter or reselect the information. This means that when a user starts an activity, they expect the activity’s transient UI state to persist until the activity is completely dismissed. So, this can be a source of frustration for the user. Furthermore, users expect an activity’s UI state to persist over configuration changes, like rotation or switching to multi-window mode. Nevertheless, by default, the system destroys the activity during such changes, and erases any UI state stored in the activity instance as well.

UI state is the property that describes the UI. There are two types of UI state:

Screen UI state is what you need to display on the screen.

UI element state refers to properties intrinsic to UI elements that influence how they are rendered.

Basically, to bridge the gap between user expectation and system behavior,
Android provides several approaches to save and restore UI states. In other words, you can be able to use a combination of ViewModel objects, saved instance state that contains the onSaveInstanceState() API in the View system, rememberSaveable in Jetpack Compose and SavedStateHandle in ViewModels, and local storage to persist the UI state across application and Activity instance transitions.

Eventually, saving UI states is vital for accessibility as well. Visually impaired users may navigate through the Android app with the help of a screen reader. If the app fails to save and restore UI states properly, it can cause the screen reader’s focus to be lost, making it challenging for the user to navigate through the app.

Some best-practices for saving UI states for Android developers

In order to save UI states in Android development effectively, it is important to follow certain best-practices and approaches. Here are some recommendations based on Google documents and resources: 1. When your Activity starts to stop, the system invokes the onSaveInstanceState() method, enabling your activity to store state information in an instance state bundle. If you want to save extra instance state information for your Activity, you can override the onSaveInstanceState() method and add key-value pairs to the Bundle object. This Bundle object is saved in case your activity gets unexpectedly destroyed. After your activity is recreated following a previous destruction, you can retrieve your saved instance state from the Bundle that the system provides to your activity. This Bundle is passed to both the onCreate() and onRestoreInstanceState() callback methods. For example:

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

if (savedInstanceState != null) {
with(savedInstanceState) {

currentPoint = getInt(STATE_POINT)
currentRate = getInt(STATE_RATE)
}
} else {
// ...
}
// ...
}

2. The method onSaveInstanceState() is not invoked when the user deliberately closes the activity or when finish() is called.

3. Rather than restoring the activity state in onCreate(), you can have an opportunity to utilize onRestoreInstanceState(), which is called by the system after onStart(). This method is just only invoked if there is a saved state to restore. Therefore, there is no need to check the Bundle.

4. To restore the state of views in your activity, they will require to have a unique ID provided by the android:id attribute in the Android system.

5. Avoid using saved instance state to hold a large volume of data, such as bitmaps, or complex data structures that need lengthy serialization or deserialization. Rather, you should store just only primitive types and simple objects, like String.

6. When UI state data, like a search query, was passed in as an intent extra during the activity’s launch, you can use the extras bundle instead of relying on the saved instance state bundle.

7. ViewModel objects provide a solution to handle configuration changes. This means it eliminates the need to worry about state loss over rotations or other similar scenarios. However, if you require to manage system-initiated process death, it could be helpful to use the SavedStateHandle API as a backup in order to persist any critical data.

ViewModel is a class that is responsible for preparing and managing the data for an Activity or a Fragment. It also handles the communication of the Activity / Fragment with the rest of the application (e.g. calling the business logic classes).

8. The choice of API to use is specified by the location of the state and the specific logic it requires. If the state is utilized in business logic, it should be stored in a ViewModel and persisted through SavedStateHandle. On the other hand, if the state is utilized in UI logic, the onSaveInstanceState API in the View system or rememberSaveable in Compose should be used. In general, logic in an Android app can be classified as business logic or UI logic:

  • Business logic is the implementation of product requirements for app data. For instance, a news reader app may implement bookmarking of an article upon the user tapping the button.
  • UI logic is related to how to display UI state on the screen, like scrolling to a specific item in a list.
The picture is provided by Google documents and resources

9. In using Jetpack Compose, if you have hoisted your state in the UI, whether in composable functions or plain state holder classes scoped to the Composition, you have the option to use rememberSaveable to persist the state across recreation of the activity and process. In fact, rememberSaveable stores UI element state in a Bundle via the saved instance state mechanism. For instance, rememberSaveable is utilized to store a single boolean UI element state as follows:

@Composable
fun ChatBubble(
message: Message
) {
var showDetails by rememberSaveable { mutableStateOf(false) }

ClickableText(
text = AnnotatedString(message.content),
onClick = { showDetails = !showDetails }
)

if (showDetails) {
Text(message.timestamp)
}
}

Jetpack Compose is Android’s recommended modern toolkit for building native UI. It simplifies and accelerates UI development on Android. Quickly bring your app to life with less code, powerful tools, and intuitive Kotlin APIs.

10. Typically, UI state is stored or referenced in ViewModel objects rather than activities. Thus, using onSaveInstanceState() or rememberSaveable needs some boilerplate, which could be managed by the saved state module. By using this module, ViewModel objects are provided with a SavedStateHandle object via their constructor. This object acts as a key-value map, and it enables you to read from and write to the saved state. Also, the SavedStateHandle just only saves data written to it when the Activity is stopped.

11. Components that want to play a role in saved state retention have to implement SavedStateRegistry.SavedStateProvider, which determines a single method called saveState(). Through this method, your component can package any state that should be saved into a Bundle. The SavedStateRegistry invokes this method over the saving state phase of the UI controller’s lifecycle. For example:

class SearchManager : SavedStateRegistry.SavedStateProvider {
companion object {
private const val QUERY = "query"
}

private val query: String? = null

...

override fun saveState(): Bundle {
return bundleOf(QUERY to query)
}
}

12. If you use persistent local storage, like a database or shared preferences in your Android application, it will remain intact as long as the user has it installed on their device. Even though this local storage can survive system-initiated activity and application process death, it can be resource-intensive to retrieve as it needs to be read from local storage into memory.

13. You should not assume that configuration changes are infrequent or will never happen. Regardless of the API level or UI toolkit, whenever a user triggers a configuration change, he/she expects that Android app adapts to the new configuration and operates perfectly.

14. Avoid imposing limitations on the orientation, aspect ratio, or resizability in order to circumvent configuration changes and activity recreation in your application. This can have adverse effects on users who want to use your Android app in a way that suits their preferences.

15. A recommended approach for saving and restoring UI state efficiently in Android development is to separate the work among various types of persistence mechanisms. The choice of mechanism for each type of data should be based on factors, such as data complexity, access speed, and lifetime. Local persistence: This stores all the application data that should not be lost when opening and closing the activity. For instance, a collection of song objects that may include audio files and metadata.

  1. Local persistence: This stores all the application data that should not be lost when opening and closing the activity. For instance, a collection of song objects that may include audio files and metadata.
  2. ViewModel: This stores all the data needed to display the associated UI in memory, including the screen UI state. For example, the song objects of the most recent search and the most recent search query.
  3. Saved instance state: This stores a small amount of data required to reload the UI state easily if the system stops and recreates the UI. It is recommended to avoid storing complex objects here and instead persist them in local storage, storing a unique ID for the objects in the saved instance state APIs. For example, you can store the most recent search query.

In conclusion

In fact, saving UI states is a crucial aspect of Android app development. By following the recommended best-practices, Android developers can ensure that their app can handle this issue effectively. This ensures that the user’s data and state are not lost during such transitions, and it provides a smooth user experience as well. This essay considered some best-practices and approaches for saving UI states in Android development based on Google documents and resources.

--

--

Kayvan Kaseb
Software Development

Senior Android Developer, Technical Writer, Researcher, Artist, Founder of PURE SOFTWARE YAZILIM LİMİTED ŞİRKETİ https://www.linkedin.com/in/kayvan-kaseb