Surviving Configuration Changes in Android

Learn how to survive configuration changes by handling your activities or fragment recreation the right way using either ViewModels, persistent storage, or doing it manually! By Beatrice Kinya.

Leave a rating/review
Download materials
Save for later
Share

An Android device consistently changes configurations. This could be a screen orientation change, keyboard availability changes or a user switching to multi-window mode. During a configuration change, Android recreates existing activities to reload resources for the new configuration. To properly handle restarting an activity, it is important to restore the activity to its previous state.

In this tutorial, you’ll build BookHub App. This app allows users to search for books using the author’s name or the book title. Along the way, you’ll learn about:

  • Saving and restoring activity state using instance state bundles.
  • Using ViewModel to store UI state.
  • Saving data in local persistent storage.
  • Manually handling configuration changes.
Note: This tutorial assumes you’re familiar with the activity lifecycle in Android. To learn about the activity lifecycle, check out the Android Activity lifecycle tutorial. If you’re completely new to Kotlin or Android development, check out Kotlin for Android: An Introduction and Android Development Series for beginners tutorials before you start.

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.

Open Android Studio and import the starter project.

Build and run the project. You’ll see this screen:

A screen showing an input field, button and text field to show the number of books fetched from remote API.

The screen has an input field for entering an author’s name or a book title to search for books. It also has a search button with a magnifying glass icon and a label to show the number of books returned from a remote API, now showing no results.

Enter an author’s name and tap the search button. The app updates the result label with the number of books returned from the API:

A screen with text field updated with the number of books returned from the remote API.

Rotate the app, though, and you’ll see a different number:

A screen in landscape orientation

Whoops! The books count got lost. When you rotated the screen, the app lost the count data because it recreated the activity to adapt to the new orientation. Your mission in this tutorial is to liven up the app while persisting data across configuration changes.

Check out the project structure:

project structure.

The project has the following packages:

  • data: This contains logic for accessing data sources such as the app database or remote APIs.
  • ui: The ui package has SearchFragment that holds an input field for entering the author’s name or book title and SearchHistoryFragment for showing the search terms the user enters. It also has BookViewModel that holds UI-related data. Fragment classes are responsible for displaying data to the user.
  • repository: Its classes receive user actions like tapping a button. Then, they forward user requests to the data layer. The repository classes also receive data from the data layer — for instance, the list of books received from the remote API. Then, they forward the data received to the UI layer.

You’ll work across various classes in these packages as you build BookHub App.

Grab your coffee! It’s gonna be amazing.

Saving UI State in Instance State Bundles

When the system recreates the UI, you can save the UI state in an instance state bundle using onSaveInstanceState() lifecycle callback.

Instance state is a collection of key-value pairs stored in a Bundle object. It’s the data the app uses to restore UI controllers like an activity or a fragment to their previous state.

You’ll implement onSaveInstanceState() callback to save the value of bookscount when a user rotates the screen.

Open SearchFragment.kt and replace // TODO 1 with the following:

override fun onSaveInstanceState(outState: Bundle) {
 super.onSaveInstanceState(outState)
 outState.putInt(bookCountKey, booksCount)
}

Here, you’re storing the value of booksCount in a Bundle object using bookCountKey key. The app calls onSaveInstanceState() immediately before calling onPause() to save the UI state.

To restore the saved state, you’ll fetch the stored value in the instance state bundle.

Replace // TODO 2 with this:

private fun restoreCount(savedInstanceState: Bundle?) {
 // 1
 booksCount = savedInstanceState?.getInt(bookCountKey) ?: 0
 // 2
 searchBinding.bookCountTextView.text = booksCount.toString()
}

Here’s what the code above does:

  1. Retrieves the value you stored in the instance state bundle using bookCountKey key and assigns it to booksCount.
  2. Restores the count pulled from saved state back to bookCountTextView.

To call the method that retrieves the value stored in the state bundles, add this just above setTextChangedListener() in onCreateView():

restoreCount(savedInstanceState)

Build and run. Enter your favorite book title and tap the search button. You’ll see the following screen:

A screen showing number of books returned when user searches alchemist in portrait orientation.

When you rotate the screen, the app retains the book count:

A screen showing number of books returned when user searches Nora Roberts in landscape orientation.

Congratulations! :]

onSaveInstanceState() serializes data to the disk. Serialization consumes a lot of memory when serializing large data sets or complex data structures such as bitmaps. This process happens in the main thread. Long-running serialization can cause UI jank during a configuration change. Therefore, you should store only primitives like Integer and small, simple objects like String in instance state bundles.

Note: Starting from Android 7.0, if you put too much data on a bundle you can encounter a TransactionTooLargeException. This is another good reason to not process big data on a bundle.

BookHub App fetches a list of books from a remote API. How do you save the list of books to prevent the app from making another API call to fetch the books in case of a configuration change? In the following section, you’ll learn how to store data in a ViewModel to address this.

Using ViewModel to Store UI State

The Android team introduced ViewModel and LiveData classes to help save state during configuration changes.

A ViewModel stores and manages UI-related data in a lifecycle-conscious manner. Simply put, it allows data to survive configuration changes. A ViewModel remains in the memory until, the Lifecycle it is scoped to, goes away completely. For an activity, this means when it finishes; for a fragment when it’s when it’s detached.

The diagram below shows the lifetime of a ViewModel next to the lifecycle of the activity it’s associated with:

ViewModel lifetime in relation to lifecycle of an activity is is scopped in

LiveData is an observable data-holder class. It’s lifecycle aware. It only notifies UI components that are in an active state when data changes.

Open BookViewModel.kt.

BookViewModel class extends ViewModel. It has three methods:

  • getBooks() to fetch books from a remote API.
  • saveSearchTerm() that saves search terms entered by the user in the app database.
  • getUserSearches() to retrieve search terms saved in the app database.

To add a LiveData object that saves the list of books fetched from the remote API, replace // TODO 3 with the following:

private val items = MutableLiveData<List<Item>?>()
val bookItems get() = items

Here, you’ve added a LiveData object that will store a list of books received from a remote API.

To store books returned from the API in items, your LiveData object, replace the getBooks() method with the following:

fun getBooks(searchTerm: String) {
 viewModelScope.launch(Dispatchers.IO) {
   // 1
   val booksInfo = bookRepository.getBookInfo(searchTerm)
   val books = booksInfo?.items
   // 2
   items.postValue(books)
 }
}

Here’s what’s happening in the code above:

  1. Calls getBookInfo() to fetch books from a remote API. The method returns a list of books.
  2. Stores the list of books received from the API in items.

To show the list of books in the UI, you’ll add an observer for bookItems.