Some device configurations can change while the app is running. These include, but are not limited to:
- App display size
- Screen orientation
- Font size and weight
- Locale
- Dark mode versus light mode
- Keyboard availability
Most of these configuration changes occur due to some user interaction. For
example, rotating or folding the device changes the amount of screen space
available to your app. Likewise, altering device settings like the font size,
language, or preferred theme changes their respective values in the
Configuration
object.
These parameters usually require large enough changes to your application's UI that the Android platform has a purpose-built mechanism for when they change. This mechanism is Activity recreation.
Activity Recreation
The system recreates an Activity when a configuration change occurs. The system
calls onDestroy()
and destroys the existing Activity instance. It then
creates a new instance using onCreate()
. This new Activity instance
initializes with the new, updated configuration. This also means that the system
also recreates the UI with the new configuration.
The recreation behavior helps your application adapt to new configurations by automatically reloading your application with alternative resources that match the new device configuration.
Recreation example
Consider a TextView
that displays a static title using
android:text="@string/title
", as defined in a layout XML file. When the view
is created, it sets the text exactly once, based on the current language. If the
language changes, the system recreates the activity. Consequently, the system
also recreates the view and initializes it to the correct value based on the new
language.
The recreation also clears out any state you have kept as fields in the Activity, or in any of its contained Fragments, Views, and other objects. This is because Activity recreation creates a completely new instance of the Activity and the UI. Furthermore, the old Activity is no longer visible or valid, so any remaining references to it or its contained objects are stale. They may cause bugs, memory leaks, and crashes.
User expectations
A user of an app expects state to be preserved. If a user is filling out a form and opens another app in multi-window mode to reference information, it would be a bad user experience if they returned to a cleared form, or to somewhere else in the app entirely. You must ensure a consistent experience through configuration changes and activity recreation.
To verify whether state is preserved in your application, you can perform actions that cause configuration changes both while the app is foregrounded and while it is in the background. These actions include:
- Rotating the device
- Entering multi-window mode
- Resizing the application while in multi-window mode or a free-form window
- Folding a foldable device with multiple displays
- Changing the system theme, such as dark mode versus light mode
- Changing the font size
- Changing the system or app language
- Connecting or disconnecting a hardware keyboard
- Connecting or disconnecting a dock
There are 3 primary approaches you can take to preserve relevant state through activity recreation. Which is relevant depends on the type of state you want to preserve:
- Local persistence to handle process death for complex or large data. Persistent local storage includes databases or DataStore.
- Retained objects such as ViewModels to handle UI-related state in memory while the user is actively using the app.
- Saved instance state to handle system-initiated process death and keep transient state that depends on user input or navigation.
The Save UI states page describes the APIs for each of these in detail, and when using each is appropriate.
Restrict Activity recreation
You can prevent automatic activity recreation for certain configuration changes. Activity recreation results in recreating the entire UI, and any objects derived from the Activity. It may be that you have good reasons to avoid doing so. For example, your app might not need to update resources during a specific configuration change, or you might have a performance limitation. In that case, you can declare that your activity handles the configuration change itself and prevent the system from restarting your activity.
You can disable activity recreation for particular configuration changes. To do
so, add the configuration type to android:configChanges
in the
<activity>
entry in AndroidManifest.xml
. Possible values appear in the
documentation for the android:configChanges
attribute.
The following manifest code disables Activity recreation for MyActivity
when
the screen orientation and keyboard availability change:
<activity
android:name=".MyActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:label="@string/app_name">
Some configuration changes always cause the activity to restart. You can't disable them. For example, you can't disable the dynamic colors change introduced in API 32. More configuration changes could behave similarly in the future.
React to configuration changes in the View system
In the View system, when a configuration change occurs for which you have
disabled activity recreation, the activity receives a call to
Activity.onConfigurationChanged()
. Any attached views also receive a
call to View.onConfigurationChanged()
. For configuration changes you
have not added to android:configChanges
, the system recreates the activity
as usual.
The onConfigurationChanged()
callback method receives a
Configuration
object that specifies the new device configuration. Reading
the fields in the Configuration
object to determine what your new
configuration should be. To make the subsequent changes, update the resources
you use in your interface. When the system calls this method, your activity's
Resources
object is updated to return resources based on the new
configuration. This lets you reset elements of your UI without the system
restarting your activity.
For example, the following onConfigurationChanged()
implementation checks
whether any keyboard is available:
Kotlin
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// Checks whether any keyboard is available
if (newConfig.keyboardHidden === Configuration.KEYBOARDHIDDEN_YES) {
Toast.makeText(this, "Keyboard available", Toast.LENGTH_SHORT).show()
} else if (newConfig.keyboardHidden === Configuration.KEYBOARDHIDDEN_NO) {
Toast.makeText(this, "No keyboard", Toast.LENGTH_SHORT).show()
}
}
Java
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Checks whether any keyboard is available
if (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_YES) {
Toast.makeText(this, "Keyboard available", Toast.LENGTH_SHORT).show();
} else if (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO){
Toast.makeText(this, "No keyboard", Toast.LENGTH_SHORT).show();
}
}
If you don't need to update your application based on these configuration
changes, you can instead not implement onConfigurationChanged()
. In which
case, all of the resources used before the configuration change are still used
and you've only avoided the restart of your activity. For example, a TV app
might not want to react when a bluetooth keyboard is attached or detached.
Retain state
When you use this technique, you must still retain state during the normal activity lifecycle. This is because of the following:
- Unavoidable changes: Configuration changes that you cannot prevent can restart your application.
- Process death: Your application should be able to handle system-initiate process death. If the user leaves your application and the app goes to the background, the system might destroy the app.
React to configuration changes in Jetpack Compose
Jetpack Compose lets your app more easily react to configuration changes. However, if you disable Activity recreation for all config changes where it is possible to do so, you must still ensure that your app correctly handles configuration changes.
The Configuration
object is available in the Compose UI hierarchy with
the LocalConfiguration
Composition local. Whenever it changes,
composable functions reading from LocalConfiguration.current
recompose. For
information about how Composition locals work, check out the Locally scoped
data with CompositionLocal documentation.
Example
In the following example, a composable displays a date with a specific format.
The composable reacts to system locale configuration changes by calling
ConfigurationCompat.getLocales()
with LocalConfiguration.current
.
@Composable
fun DateText(year: Int, dayOfYear: Int) {
val dateTimeFormatter = DateTimeFormatter.ofPattern(
"MMM dd",
ConfigurationCompat.getLocales(LocalConfiguration.current)[0]
)
Text(
dateTimeFormatter.format(LocalDate.ofYearDay(year, dayOfYear))
)
}
To avoid Activity recreation when the locale changes, the Activity hosting the
Compose code needs to opt out of locale configuration changes. To do so, you
should set android:configChanges
to locale|layoutDirection
.
Configuration changes key concepts and ideas
As a summary, here's what you need to know when working on configuration changes:
- Configurations: Device configurations define how the UI should display to the user such as app display size, locale, or system theme.
- Configuration changes: Configurations change through user interaction. For example, the user might change device settings or how they physically interact with the device. The configuration will change; there's no way to "prevent" configuration changes.
- Activity recreation: Configuration changes result in Activity recreation by default. This is a built-in mechanism to re-initialize app state for the new configuration.
- Activity destruction: Activity recreation causes the system to destroy the old Activity instance and create a new one in its place. The old instance is now obsolete; any remaining references to it will result in memory leaks, bugs, or crashes.
- State: State in the old Activity instance is not present in the new Activity instance, because they are two different object instances. Preserve the app and user's state as described in Save UI states.
- Opt-out: Opting out of activity recreation for a type of configuration change is a potential optimization. It requires you to ensure that your app properly updates in reaction to the new configuration.
Best practices
In order to provide a good user experience, observe the following:
- Configuration changes are frequent: Don't assume configuration changes are rare or never happen, regardless of API level, form factor, or UI toolkit. When a user causes a configuration change, they expect apps to update and continue to work correctly with the new configuration.
- Preserve state: Don't lose the user's state when Activity recreation occurs. Preserve the state as described in Save UI states.
- Avoid opting out as a quick fix: Don't opt-out of Activity recreation as a shortcut to avoid state loss. Opting out of activity recreation requires fulfilling the promise of handling the change, and you could still lose the state due to Activity recreation from other configuration changes, process death, or closing the app. It is impossible to entirely disable Activity recreation. Preserve the state as described in Save UI states.
- Don't avoid configuration changes: Don't put restrictions on orientation, aspect ratio, or resizability in order to avoid configuration changes and Activity recreation. This negatively impacts users who want to use your app in their preferred way.
Handle size-based config changes
Size-based configuration changes can happen at any time. This happens more often when your app runs on a large screen device where users can enter the multi-window mode. They would expect your app to work well in that environment.
There are two general types of size changes: significant and insignificant. A "significant" size change is one where a different set of alternative resources apply to the new configuration due to a difference in screen size, such as width, height, or smallest width. These resources include those that the app defines itself, and any of its libraries containing resources.
Restrict Activity recreation for size-based config changes
When you disable Activity recreation for size-based configuration changes , the
system doesn't recreate the Activity. Instead, it receives a call to
Activity.onConfigurationChanged()
. Any attached views receive a call to
View.onConfigurationChanged()
.
Allow Activity recreation for size-based config changes
On API 24 and above, Activity recreation only occurs for size-based
configuration changes if the size change is significant. When the system doesn't
recreate an Activity due to insufficient size, the system may call
Activity.onConfigurationChanged()
and
View.onConfigurationChanged()
instead.
There are some caveats you should observe regarding the Activity and View callbacks:
- On API 30 and above, the
Activity.onConfigurationChanged()
callback won't be called. - On API 32 and API 33,
View.onConfigurationChanged()
won't be called. This is a bug that was fixed in future API versions.
For code that is dependent on listening for size-based configuration
changes, it is recommended to use a utility View with an overridden
View.onConfigurationChanged()
instead of relying on Activity recreation or
Activity.onConfigurationChanged()
.