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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Surviving Configuration Changes in Android
20 mins
- Getting Started
- Saving UI State in Instance State Bundles
- Using ViewModel to Store UI State
- Observing LiveData Changes
- Understanding Room Library
- Looking Into Data Entities
- Understanding Data Access Objects
- Exploring Database Class
- Saving a Search Term
- Reading Data From the App Database
- Managing Configuration Changes Yourself
- Where to Go From Here?
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.
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:
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:
Rotate the app, though, and you’ll see a different number:
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:
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 andSearchHistoryFragment
for showing the search terms the user enters. It also hasBookViewModel
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:
- Retrieves the value you stored in the instance state bundle using
bookCountKey
key and assigns it tobooksCount
. - 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:
When you rotate the screen, the app retains the book count:
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.
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:
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:
- Calls
getBookInfo()
to fetch books from a remote API. The method returns a list of books. - 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
.