Implement dark theme support for android application using Kotlin

Nitin Berwal
Naukri Engineering
Published in
6 min readMar 26, 2024

Dark mode has become increasingly popular over the years, as it not only helps save battery life but also reduces eye strain, especially during night time use. For Android developers using Kotlin, adding dark theme support to their apps is now easier than ever before.

Android has built-in support for dark theme, starting from Android 10 (API level 29). Developers can use this feature to provide users with a consistent and enjoyable user experience, no matter what time of day they use the app.

In the above you can see that we can have custom switch button to change theme of the app as well as we can change through mobile default theme button. We can also observe that image is changes as well as text colour and background. On changing theme we can changes everything like drawable, text colour, text style, background colour etc.

Here are the common steps for both the approaches to add dark theme support to an Android app either custom switch button approach or default theme button using Kotlin.

  1. Define themes files, to support dark theme, developers need to define two themes — a light theme and a dark theme. The light theme is the default theme that the app uses, while the dark theme is used when the user switches to dark mode.
  2. Create a new values folder to define the dark theme resources, Create a new styles.xml file that defines the dark theme and so on for colours, themes etc. This file should contain the same names for styles as the regular(light) styles.xml file, but with different values for text and background colours.
  3. In the addition of above step you can also support different drawable for light and dark theme. In the same way of step 2 right click on drawable->New->New Drawable resource file then give the same name(which is for light theme) for drawable and then select Night mode from Available qualifiers

After that click on right double arrow

Then click OK and use the image by referring it from drawable folder. Android will automatically pick resources from night folder if your mobile default theme is dark or you have set theme manually.

4. Define colours in the app’s colours.xml file. By using colour resources, it becomes easier to switch between light and dark themes. Define different values for the same colour name for both light and dark themes. Remove all hard coded colours values(in xml or in code) and refer it from app’s colours.xml only.

5. Use AppCompat themes to make it easier to support both dark and light themes. These themes provide backward compatibility and work with all versions of Android.

Now we will implement custom switch button for theme change. First of all add below code to your activity_main or in any xml file where you want to give switch button:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/ivProfilePic"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="180dp"
android:background="@drawable/bkgd_rectangle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_photo" />

<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvHeading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/headingText"
android:textColor="@color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/ivProfilePic"
tools:text="Dark theme" />

<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:text="@string/enableDarkTheme"
android:textColor="@color/black"
app:layout_constraintBottom_toBottomOf="@id/btnSwitch"
app:layout_constraintEnd_toStartOf="@id/btnSwitch"
app:layout_constraintTop_toTopOf="@id/btnSwitch" />

<androidx.appcompat.widget.SwitchCompat
android:id="@+id/btnSwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginEnd="100dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvHeading" />


</androidx.constraintlayout.widget.ConstraintLayout>

Now we can see that I am referring every values of text colour from colors.xml file so that android can pick resources from colors.xml light if theme is light or colors.xml(night) if theme is dark.

You can verify your resources value configuration from below image it should contain two folders for each value one for light mode other for night mode.

//Paste the below strings in your strings.xml
<string name="headingText">Light theme enabled</string>
<string name="enableDarkTheme">Enable dark theme</string>

//Paste the below strings in your strings.xml (night)
<string name="headingText">Dark theme enabled</string>
<string name="enableDarkTheme">Enable light theme</string>


//Paste the below color in your colors.xml
<color name="black">#FF000000</color>

//Paste the below color in your colors.xml(night)
<color name="black">#FFFFFFFF</color>v
package com.example.darkthemesample

import android.content.res.Configuration
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.widget.SwitchCompat

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

val switchButtonView = findViewById<SwitchCompat>(R.id.btnSwitch)

// Below code is used to write some code that is used only in one theme either it is light or dark
val currentNightMode = (resources.configuration.uiMode
and Configuration.UI_MODE_NIGHT_MASK)
when (currentNightMode) {
Configuration.UI_MODE_NIGHT_NO -> {
// Do something in light theme

//adding below code to save current theme in prefrences
sharedPreferences.edit().putInt(UI_MODE, UI_MODE_LIGHT).apply()
}
Configuration.UI_MODE_NIGHT_YES -> {
//Do something in dark theme

//adding below code to save current theme in prefrences
sharedPreferences.edit().putInt(UI_MODE, UI_MODE_DARK).apply()
}
Configuration.UI_MODE_NIGHT_UNDEFINED -> {}
}

// Switching theme manually using switch button
switchButtonView.setOnCheckedChangeListener{ _, isChecked ->
if(isChecked){
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
}
}
}
}

Here is the code to switch theme. Here we applied setCheckedChangeListener on switch button and then setting default theme using AppCompatDelegate.

Finally its time to test our app,

First approach of changing theme using switch button is completed. Test the app on both light and dark modes to ensure that everything is working as expected.

For the second approach of theme switching by switching the theme of mobile. You don’t have to do any extra work. Just test your app by changing your device’s theme. Your app will pick resources accordingly.

The only thing you will have to keep in mind before testing your app by changing device theme is that it will work only if you haven’t set your app’s theme by switch manually.( To check device theme switching, just clear your app’s data so that the variable for theme is cleared which we put on sharedPreference and then run your app and change device’s theme it will work correctly)

Miscellaneous Problems in switching theme

There were several problem that I found during development of dark theme support is that If we changes default theme of mobile at app runtime then in some case android is referring it from old theme resources i.e. it is not updating drawable/text color according to current theme. For this problem we will have to use below:

Add below code in AndroidManifest.kt file

<application
android:name=".base.application.ApplicatioName"
.
.
android:configChanges="uiMode"
.
.
const val UI_MODE="theme"
const val UI_MODE_DARK = 1
const val UI_MODE_LIGHT = 0


fun getCurrentThemeResources(context: Context): Resources {
val desiredTheme = when (getUiModeValue()) {
UI_MODE_LIGHT -> 1
UI_MODE_DARK -> 2
else -> 1
}
var conf: Configuration = context.resources.configuration
conf = Configuration(conf)
conf.uiMode = desiredTheme
val localizedContext = context.createConfigurationContext(conf)
return localizedContext.resources
}


fun getUiModeValue(): Int {
return sharedPreferences.getInt(UI_MODE, UI_MODE_LIGHT)?: UI_MODE_LIGHT
}

Above function will return updated resources according to current theme. And using those resources we can also get updated values of drawable, colors etc.

We can use the resources returned by above function( getCurrentThemeResources() ) as used below:

binding.tvHeading.setTextColor(
getCurrentThemeResources(context).getColor(
R.color.colorRed
)
)

Second problem I have faced is that in some cases we can also use below code to get updated colors, drawable etc values

val color = ContextCompat.getColor(context, R.color.colorBlue)

Conclusion

So, in this way we can implement dark mode for our android applications.

If you have any queries or suggestions please post in the comment section below.

--

--