Android: Toggling Your App's Theme
I was recently asked how an app's theme can be toggled by the user in Android. This is a nice feature that is often seen in reading apps.
I've always found Android's approach to theming overwhelmingly complex, but with the Android 5 Lollipop release and Design Support Library, Google have made great progress in simplifying things.
Recent builds of Android Studio have also included an early Theme Editor which can be used to edit and preview your app's themes and how it will affect various widgets.
In this tutorial, we'll build a simple app with a switch that lets the user toggle between dark and light themes.
The full source code for this tutorial is available at https://github.com/cblunt/blog-android-theme-toggler.
$ git clone [email protected]:cblunt/blog-android-theme-toggler.git
Create the project
Note: For this project, I'm using the latest Android Studio 2.0 beta release (preview2).
We'll start by creating a new application:
-
Create a new project called ThemeToggler targetting API Level 15 (Ice Cream Sandwich). We'll use the Android Design Support library to support older devices running Android 4.
-
Choose Blank Activity to create a basic Activity with a Floating Action button.
-
Accept the defaults for the remaining screens to create your new application.
Build the UI
Once generated, we'll add a switch widget for the user to toggle the app's theme:
- Delete the Hello World label from the default layout that was created.
- Add a new
Switch
widget to the layout with the following attributes:
<!-- res/layout/content_main.xml -->
<Switch
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Toggle Theme"
android:id="@+id/switch1"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true" />
Next, we'll hook up our switch to toggle the application's theme:
- Add an
OnCheckedChangeListener
to the Switch in your Activity. The state of the Switch will be used to determine which theme is used:
// app/src/main/java/com/example/themetoggler/MainActivity.java
public class MainActivity extends AppCompatActivity {
// ...
protected void onCreate(Bundle savedInstanceState) {
// ...
Switch toggle = (Switch) findViewById(R.id.switch1);
toggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton view, boolean isChecked) {
toggleTheme(isChecked);
}
});
}
Saving the User's Choice
When the theme is toggled, we'll need to restart the current Activity to use the chosen theme. This is similar to configuration changes in Android (e.g. screen rotation) where the system will destroy and recreate the current Activity.
We'll also need to store the user's chosen theme so the re-created Activity knows which theme to use. It makes sense to store this in the app's SharedPreferences
so that the user's choice is persisted across application launches.
After storing the user's preference, we store a reference to the Activity's original Intent, finish the current activity and restart it using that original Intent:
- Add a new method,
toggleTheme()
to MainActivity to store the user's preference and restart the current Activity:
// app/src/main/java/com/example/themetoggler/MainActivity.java
public class MainActivity extends AppCompatActivity {
private static final String PREFS_NAME = "prefs";
private static final String PREF_DARK_THEME = "dark_theme";
// ...
private void toggleTheme(boolean darkTheme) {
SharedPreferences.Editor editor = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit();
editor.putBoolean(PREF_DARK_THEME, darkTheme);
editor.apply();
Intent intent = getIntent();
finish();
startActivity(intent);
}
}
Creating the Theme
Finally, we'll need to change the Activity's onCreate
method to use the user's preferred theme.
However, we haven't created any alternative themes yet, so let's do that first.
Let's add an alternative AppTheme.Dark
which extends the default Theme.AppCompat
. We can use the original AppTheme
that Android Studio created as the basis for our theme:
- Create
AppTheme.Dark
instyles.xml
:
<!-- app/src/main/res/values/styles.xml -->
<style name="AppTheme.Dark" parent="Theme.AppCompat">
<!-- Dark theme base colours -->
<item name="colorPrimary">@color/darkColorPrimary</item>
<item name="colorPrimaryDark">@color/darkColorPrimaryDark</item>
<item name="colorAccent">@color/darkColorAccent</item>
</style>
- We'll also need to override some of the system themes, such as the
NoActionBar
theme. Use the existingAppTheme.
styles as a base:
<!-- app/src/main/res/values/styles.xml -->
<style name="AppTheme.Dark.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.Dark.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppTheme.Dark.PopupOverlay" parent="ThemeOverlay.AppCompat" />
- I've also used declared colours in the theme. Put these in your
colors.xml
resource file:
<!-- app/src/main/res/values/colors.xml -->
<color name="darkColorPrimary">#1e2756</color>
<color name="darkColorPrimaryDark">#141831</color>
<color name="darkColorAccent">#2aac4b</color>
We've now declared enough for our themes to be used in the app. You can use Android Studio's experimental Theme Editor to preview and tweak your themes:
- Choose Tools->Android->Theme Editor
- In the Theme dropdown, choose your
AppTheme
andAppTheme.Dark
themes to preview how different widgets will look:
Applying the theme
Finally, back in our code, we can instruct our Activity to use the selected theme in onCreate()
.
Note that the code must be at the top of onCreate()
(before the call to super.onCreate()
) so that the app's default theme (specified in AndroidManifest.xml
) is overridden
// app/src/main/java/com/example/themetoggler/MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
// Use the chosen theme
SharedPreferences preferences = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
boolean useDarkTheme = preferences.getBoolean(PREF_DARK_THEME, false);
if(useDarkTheme) {
setTheme(R.style.AppTheme_Dark_NoActionBar);
}
super.onCreate(savedInstanceState);
// ...
- Also in
onCreate()
, we'll update the switch to reflect the current choice (make sure this line goes before you add theonCheckedChangedListener
to prevent an infinite loop:
// app/src/main/java/com/example/themetoggler/MainActivity.java
Switch toggle = (Switch) findViewById(R.id.switch1);
toggle.setChecked(useDarkTheme);
toggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
// ...
Finally, run your app and tap the switch to toggle between your dark and light themes. With every toggle, the Activity will be restarted.
This tutorial introduces how you can give the user a choice of theme for your app. The code you added for applying theme (at the top of onCreate()
) would be common across all the Activities in your app.
For this reason, it would be a good thing to extract into a BaseActivity
from which all your app's Activities inherit.
I hope you've found this tutorial useful. Please let me know your thoughts and feedback in the comments below, or by getting in touch on Twitter (@cblunt).
Full Source Code: https://github.com/cblunt/blog-android-theme-toggler
👋 Thanks for reading - I hope you enjoyed this post. If you find it helpful and want to support further writing and tutorials like this one, please consider supporting my work with a coffee!
Support ☕️