In Android development, handling configuration changes is a crucial aspect of creating robust and user-friendly applications. Configuration changes, such as screen rotation, keyboard availability, or locale changes, can lead to the Activity being destroyed and recreated. If not handled properly, this can result in data loss and a poor user experience. This article focuses on effectively managing configuration changes in XML-based UI environments.
Understanding Configuration Changes
Configuration changes occur when the device undergoes a change in its setup, such as screen orientation, keyboard availability, locale, or even multi-window mode. By default, Android handles most configuration changes by restarting the Activity. While this ensures that the app adapts to the new configuration, it can also lead to data loss and performance issues if the Activity is recreated without proper handling.
Why Proper Handling is Essential
- Data Preservation: Prevents loss of UI state data like input values or displayed data.
- User Experience: Maintains a smooth and uninterrupted experience during configuration changes.
- Performance: Avoids unnecessary resource consumption by preventing the Activity from being fully recreated when not necessary.
Basic Approach: Saving and Restoring State
The most basic approach to handle configuration changes is to save the Activity’s state before it is destroyed and restore it when the Activity is recreated. Android provides two primary mechanisms for this:
onSaveInstanceState()
methodViewModel
and related lifecycle components (discussed later)
Using onSaveInstanceState()
The onSaveInstanceState()
method allows you to save key data that can be used to restore the Activity’s state.
Step 1: Override onSaveInstanceState()
Override the onSaveInstanceState()
method in your Activity:
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("my_text", myTextView.getText().toString());
outState.putInt("my_integer", myIntegerValue);
}
Step 2: Restore the State in onCreate()
In the onCreate()
method, restore the saved state from the savedInstanceState
Bundle:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myTextView = findViewById(R.id.my_text_view);
if (savedInstanceState != null) {
String savedText = savedInstanceState.getString("my_text");
int savedInteger = savedInstanceState.getInt("my_integer");
myTextView.setText(savedText);
myIntegerValue = savedInteger;
} else {
// Initial setup if no state was saved
myTextView.setText("Hello, World!");
myIntegerValue = 0;
}
}
Example Scenario
Imagine you have an EditText field where the user enters some text. Without handling configuration changes, rotating the screen would cause the text to disappear.
<EditText
android:id="@+id/my_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter text here"/>
Here’s how you could preserve the text using onSaveInstanceState()
:
private EditText myEditText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myEditText = findViewById(R.id.my_edit_text);
if (savedInstanceState != null) {
String savedText = savedInstanceState.getString("edit_text_value");
myEditText.setText(savedText);
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("edit_text_value", myEditText.getText().toString());
}
Advanced Approach: Using ViewModel
For more complex data, managing state directly in the Activity can become cumbersome. Android’s ViewModel
, as part of the Architecture Components, provides a lifecycle-aware way to manage UI-related data.
Adding Dependencies
Include the necessary dependencies in your build.gradle
file:
dependencies {
implementation "androidx.lifecycle:lifecycle-viewmodel:2.6.1"
implementation "androidx.lifecycle:lifecycle-livedata:2.6.1"
annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.6.1"
}
Creating a ViewModel Class
Define a ViewModel class that holds the UI-related data:
import androidx.lifecycle.ViewModel;
public class MyViewModel extends ViewModel {
private String myText;
public String getMyText() {
return myText;
}
public void setMyText(String myText) {
this.myText = myText;
}
}
Using ViewModel in Activity
In your Activity, access and use the ViewModel:
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
import android.os.Bundle;
import android.widget.EditText;
public class MainActivity extends AppCompatActivity {
private MyViewModel viewModel;
private EditText myEditText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myEditText = findViewById(R.id.my_edit_text);
viewModel = new ViewModelProvider(this).get(MyViewModel.class);
if (viewModel.getMyText() != null) {
myEditText.setText(viewModel.getMyText());
}
myEditText.setOnTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
viewModel.setMyText(s.toString());
}
@Override
public void afterTextChanged(Editable s) {}
});
}
}
Using a ViewModel
ensures that the data persists even when the Activity is destroyed and recreated during configuration changes.
Handling Configuration Changes Manually
Sometimes, you might want to prevent the Activity from being recreated altogether. In such cases, you can handle specific configuration changes manually.
Step 1: Declare the Change in the Manifest
In your AndroidManifest.xml
, declare the configuration changes you want to handle by adding the android:configChanges
attribute to the <activity>
tag.
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
This example declares that the Activity will handle orientation and screen size changes itself.
Step 2: Override onConfigurationChanged()
Override the onConfigurationChanged()
method in your Activity to handle the changes.
import android.content.res.Configuration;
import android.os.Bundle;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Check the orientation of the screen
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
}
}
}
When a declared configuration changes, onConfigurationChanged()
is called instead of restarting the Activity.
Best Practices for Handling Configuration Changes
- Use ViewModel for UI-related Data: For complex data that needs to survive configuration changes, use
ViewModel
. onSaveInstanceState()
for Simple UI State: UseonSaveInstanceState()
for saving simple UI states like EditText values.- Manual Handling Judiciously: Only handle configuration changes manually if necessary and if you can efficiently manage all the related logic.
- Avoid Complex Logic in
onConfigurationChanged()
: Keep theonConfigurationChanged()
method light to avoid performance bottlenecks. - Test Thoroughly: Always test your application with different configuration changes to ensure data is preserved and the UI behaves as expected.
Conclusion
Effectively handling configuration changes in Android is crucial for providing a seamless user experience and preventing data loss. Using techniques like onSaveInstanceState()
, ViewModel
, and manual handling with onConfigurationChanged()
, you can build robust applications that gracefully adapt to various device configurations. Properly implemented, these strategies contribute significantly to the overall quality and usability of your Android app. Consider carefully whether it’s appropriate to prevent restarts due to configuration changes since these restarts are often necessary to load appropriate resources for the new configuration.