Dark Mode for Android App in Kotlin using DataStore Jetpack android

Dark Theme Night Mode implementation programmatically Android App Development Tutorial for beginners

  • Dark Mode is an example of optimization of User Experience as well as the Battery. 
  • It can be implemented on any application a developer desires.

DataStore

  • Jetpack DataStore is a data storage solution that allows you to store key-value pairs (Google’s new library). 
  • It aims to replace SharedPreferences.
  • DataStore uses Kotlin coroutines and Flow to store data asynchronously, consistently, and transactionally.


Google Material Design Color 

  • Build a custom theme and export it to code. 

..

How to implement Dark (Night) mode in Android using  DataStore?

Step 1: 

Add datastore dependency in build.gradle file of your app module

 // Preference DataStore
    implementation "androidx.datastore:datastore-preferences:1.0.0"

Step 2: 

Create a Preferences DataStore

  • The Preferences DataStore implementation uses the DataStore and Preferences classes to persist simple key-value pairs to disk.
  • At the top level of your kotlin file

class UIModePreference(var context: Context) {

    // At the top level of your kotlin file:
    private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "ui_mode_preference")

}

Step 3: 

Read from a Preferences DataStore

  • To define a key for an int value, use intPreferencesKey(). 
  • Use the DataStore.data property to expose the appropriate stored value using a Flow.

 val uiMode: Flow<Boolean> = context.dataStore.data
        .map { preferences ->
            val uiMode = preferences[UI_MODE_KEY] ?: false
            uiMode
        }

..

Step 4: 

Write to a Preferences DataStore

  • Preferences DataStore provides an edit() function that transactionally updates the data in a DataStore
  • The function's transform parameter accepts a block of code where you can update the values as needed. All of the code in the transform block is treated as a single transaction.

  suspend fun saveToDataStore(isNightMode: Boolean) {
        context.dataStore.edit { preferences ->
            preferences[UI_MODE_KEY] = isNightMode
        }
    }

Get this code for the common class for the data store to read & write daylight mode switching boolean value UIModePreference.kt

Step 5: 

Set the menu in our fragment to switch daylight mode in a toolbar menu item.

  • Add menu/menu_main.xml in your res folder

<?xml version="1.0" encoding="utf-8"?>

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/action_night_mode"
        android:checkable="true"
        android:icon="@drawable/ic_day"
        android:title="@string/text_ui_mode"
        app:showAsAction="ifRoom" />

</menu>

  • Add the following code in your fragment to handle the menu item

 //'setHasOptionsMenu(Boolean): Unit' is deprecated. Deprecated in Java
        //https://stackoverflow.com/questions/71917856/sethasoptionsmenuboolean-unit-is-deprecated-deprecated-in-java
        // The usage of an interface lets you inject your own implementation

        val menuHost: MenuHost = requireActivity()
        // Add menu items without using the Fragment Menu APIs
        // Note how we can tie the MenuProvider to the viewLifecycleOwner
        // and an optional Lifecycle.State (here, RESUMED) to indicate when
        // the menu should be visible
        menuHost.addMenuProvider(object : MenuProvider {
            override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
                // Add menu items here
                menuInflater.inflate(R.menu.menu_main, menu)


            }
            override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
                // Handle the menu selection
                return when (menuItem.itemId) {
                    R.id.action_night_mode -> {
                        //..
                        true
                    }
                    else -> false
                }
            }
        }, viewLifecycleOwner, Lifecycle.State.RESUMED)

...

Step 6:

Applying Dark mode
AppCompatDelegate
.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) // night mode
AppCompatDelegate
.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) // day mode
//this will follow system settings (from notification bar)
AppCompatDelegate
.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)

SetUIMode() is a custom function, used to apply day/light mode, save boolean mode values in the data store & change the menu icon according to mode
  private fun setUIMode(item: MenuItem, isChecked: Boolean) {
        if (isChecked) {
            AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
            viewModelActivity.saveToDataStore(true)
            item.setIcon(R.drawable.ic_night)

        } else {
            AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
            viewModelActivity.saveToDataStore(false)
            item.setIcon(R.drawable.ic_day)

        }
    }
We use the view model to store and retrieve the mode value from the datastore.

Here is the sample view model
class NotesViewModel(application: Application) :
    AndroidViewModel(application) {

    // DataStore
    private val uiDataStore = UIModePreference(application)

    // get UI mode
    val getUIMode = uiDataStore.uiMode

    // save UI mode
    fun saveToDataStore(isNightMode: Boolean) {
        viewModelScope.launch(IO) {
            uiDataStore.saveToDataStore(isNightMode)
        }
    }

 ..

GET source code on Github:

Comments