Cover Photo by Maxim Ilyahov on Unsplash
Hello, friends!
To introduce myself, I am Neeyat Lotlikar, an Android Developer. I am currently working on my portfolio and I'd like to share what I've learned recently while working on it. This is my first post so feel free to make any corrections and suggestions.
Without any further delay, let's get going!
The first step to enable dark mode is to change your app theme to Theme.AppCompat.DayNight
or Theme.MaterialComponents.DayNight
if you're using the Material Design library.
res/values/styles.xml
<style name="AppTheme" parent="Theme.AppCompat.DayNight">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
Next, create a Preference activity.
This can be done with the help of the Android Studio templates with ease or you can even do it manually.
SettingsActivity.kt
class SettingsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.settings_activity)
supportFragmentManager
.beginTransaction()
.replace(R.id.settings, SettingsFragment())
.commit()
}
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
}
}
}
We want a ListPreference
in our root_prefernces.xml file and so it should look something like this:
res/values/xml/root_preferences.xml
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory app:title="@string/ui">
<ListPreference
app:defaultValue="@string/dark_mode_def_value"
app:entries="@array/dark_mode_entries"
app:entryValues="@array/dark_mode_values"
app:key="@string/dark_mode"
app:title="@string/dark_mode" />
</PreferenceCategory>
</PreferenceScreen>
Here, the app:defaultValue
is used to set a default value which is stored in strings.xml. The app:title
and app:key
attribute values specify the display title of the preference and the key to identify it by respectively.
res/values/strings.xml
<resources>
<!-- Preference Titles -->
<string name="ui">UI</string>
<string name="dark_mode">Dark Mode</string>
<string name="dark_mode_def_value">MODE_NIGHT_FOLLOW_SYSTEM</string>
</resources>
app:entries
and app:entryValues
attributes take array values that specify the corresponding display name and the value it carries so make sure they are in the proper order. I have added my arrays to arrays.xml as below:
res/values/arrays.xml
<resources>
<!-- Dark Mode ListPreference -->
<string-array name="dark_mode_entries">
<item>Follow System Dark Mode</item>
<item>Light Mode Selected</item>
<item>Dark Mode Selected</item>
<item>Auto Battery Dark Mode</item>
</string-array>
<string-array name="dark_mode_values">
<item>MODE_NIGHT_FOLLOW_SYSTEM</item>
<item>MODE_NIGHT_NO</item>
<item>MODE_NIGHT_YES</item>
<item>MODE_NIGHT_AUTO_BATTERY</item>
</string-array>
</resources>
Now that we are done with the layout, let's look at the code.
The AppCompatDelegate
class' setDefaultNightMode()
function is used to change the night mode settings. Following values can be passed to this method:
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
- This will toggle the night mode based on whether the system-wide dark mode is enabled or not.
AppCompatDelegate.MODE_NIGHT_NO
- Light mode will be enabled.
AppCompatDelegate.MODE_NIGHT_YES
- Dark/Night mode will be enabled.
AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
- Dark/Night mode will be enabled when the device is on the battery saving mode.
So where do we use this method?
Implement SharedPreferences.OnSharedPreferenceChangeListener
in SettingsActivity
and override the onSharedPreferenceChanged()
function.
SettingsActivity.kt
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
val darkModeString = getString(R.string.dark_mode)
key?.let {
if (it == darkModeString) sharedPreferences?.let { pref ->
val darkModeValues = resources.getStringArray(R.array.dark_mode_values)
when (pref.getString(darkModeString, darkModeValues[0])) {
darkModeValues[0] -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
darkModeValues[1] -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
darkModeValues[2] -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
darkModeValues[3] -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY)
}
}
}
}
As you can see, I retrieve the preference in the above code and use it to determine which option is selected in the list preference. Using this, the night mode settings are changed.
Now, a very important thing to do is registering and unregistering the OnSharedPreferenceChangeListener
. This will ensure that the changes are made almost instantly on user input and it also prevents memory leakage.
Register the listener in the onCreate()
function and unregister it in the onDestroy()
function.
SettingsActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.settings_activity)
supportFragmentManager
.beginTransaction()
.replace(R.id.settings, SettingsFragment())
.commit()
supportActionBar?.setDisplayHomeAsUpEnabled(true)
PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener(this)
}
override fun onDestroy() {
super.onDestroy()
PreferenceManager.getDefaultSharedPreferences(this)
.unregisterOnSharedPreferenceChangeListener(this)
}
Now, a provision has to be made to make navigation to the SettingsActivity
possible which I did by adding an action to the options menu. You can use whatever works best for you.
It is possible to add separate icons, colors, styles, and other resources to be used in the day and the night mode. You do so by adding these resources in resource folders with the extension of -night
. For example, drawable-night
or values-night
.
And with this, we've successfully got our dark mode toggle working.
There's a problem, however. As you can see, the ListPreference
doesn't show which option is currently selected in the summary. How do we fix that? Well, that's very simple actually. There are just two things that need to be done here:
Firstly, set app:useSimpleSummaryProvider
attribute value to true in ListPreference
.
res/xml/root_preferences.xml
<ListPreference
app:defaultValue="@string/dark_mode_def_value"
app:entries="@array/dark_mode_entries"
app:entryValues="@array/dark_mode_values"
app:key="@string/dark_mode"
app:title="@string/dark_mode"
app:useSimpleSummaryProvider="true" />
Secondly, implement the Preference.SummaryProvider<ListPreference>
interface in SettingsActivity
and override it's provideSummary()
function.
SettingsActivity.kt
override fun provideSummary(preference: ListPreference?): CharSequence =
if (preference?.key == getString(R.string.dark_mode)) preference.entry
else "Unknown Preference"
Note that I am using androidx.preference.Preference
. SummaryProvider
is not available in android.preference.Preference
i.e. the android package.
And with this, this tutorial is completed.
Check out the full code on my GitHub here:
neeyatl / DarkModePreferencesTutorial
A tutorial app as a guide for implementing Night/Dark Mode using ListPreference in Android.
DarkModePreferencesTutorial
A tutorial app as a guide for implementing Night/Dark Mode using ListPreference in Android.
This tutorial blog teaches how to change the app theme using the androidx.appcompat.app.AppCompatDelegate
class.
It also teaches how to use ListPreference
from the androidx.preference
package to create a switch in the in-app settings (activity) to allow the user to choose the setting that she/he prefers.
See the full tutorial blog post: https://dev.to/aurumtechie/implement-dark-night-mode-in-an-android-app-with-a-listpreference-toggle-2k5i
This is the Kotlin implementation of this app. For it's java version, visit: https://github.com/Aurum1611/DarkModePreferenceTutorialJava
Upon request, I've created a Java counterpart for the project:
neeyatl
/
DarkModePreferenceTutorialJava
Part of a tutorial that shows how to create Dark Mode in Android. This project is written in Java and is a counterpart to DarkModePreferenceTutorial, the Kotlin version.
DarkModePreferenceTutorialJava
A tutorial app as a guide for implementing Night/Dark Mode using ListPreference in Android.
This tutorial blog teaches how to change the app theme using the androidx.appcompat.app.AppCompatDelegate
class.
It also teaches how to use ListPreference
from the androidx.preference
package to create a switch in the in-app settings (activity) to allow the user to choose the setting that she/he prefers.
See the full tutorial blog post: https://dev.to/aurumtechie/implement-dark-night-mode-in-an-android-app-with-a-listpreference-toggle-2k5i
This is the Java implementation of this app. For it's Kotlin version, visit: https://github.com/Aurum1611/DarkModePreferencesTutorial
I hope this is helpful. Happy coding!
Top comments (9)
Love the post, but i cant seem to get it to work. Could you assist?
i get the: "'onSharedPreferenceChanged' overrides nothing" error
also:
gives me: Type mismatch.
Required:
SharedPreferences.OnSharedPreferenceChangeListener!
Found:
SettingsActivity
Have you implemented the interface? Make sure you have implemeted the
SharedPreferences.OnSharedPreferenceChangeListener
interface in theSettingsActivity
.If you're using the same code, I believe the second problem should go away with the same correction.
Sorry for the late reply. Wasn't online these days... Let me know how it goes.
Yes, thanks, i figured it out. I'm pretty new to Kotlin, so i wasn't clear on "how to implement". Eventually i figured out it was just a comma seperator.
class SettingsActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceChangeListener
otherwise very grateful for the explanation. Thanks
I'm glad you got it. All the best!
Thank you for this. There is a problem. If you select dark mode, force-close the app and reopen the app, it automatically changes to light mode. The option selected was still in dark mode though.
You will have to also use shared preferences inside your startup/launcher activity to set the dark mode to your desired setting once the activity is created. I have made these changes in the github repo. Take a look. Hope it's helpful.
Awesome. One more thing, will the code change if i want to use SwitchPreference?
Not much really. Even the methods used should remain the same. Switch preference will have to be implemented and that's pretty much it.
Okay, that really helps.