Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ContextCompat.getColor() ignore NightMode

TL,DR;

ContextCompat.getColor() does not use the night colors (values-night/colors.xml) though it should when night mode is enabled.

Here is the problem:

Hi everyone,

So I'm implementing a dark theme for my Android app, I call this to enable it : AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);

I have set colors in values/colors.xml and there dark version in values-night/colors.xml. The colors changes well depending on the nightMode, BUT :

when I use ContextCompat.getColor(getApplicationContext(), R.id.myColor), it uses the normal colors (values/colors.xml) and not the night colors (values-night/colors.xml).

In my build.gradle, I have set these :

implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.2.0-beta01'

Could someone please tell me what am I doing wrong ?

PS : I already looked at the following question and it does not answer this problem https://stackoverflow.com/questions/57779661/contextcompat-getcolor-method-ignores-night-mode

like image 695
Mathieu de Brito Avatar asked Oct 10 '19 12:10

Mathieu de Brito


Video Answer


3 Answers

I faced similar issues with night mode. Some screens were fine but others were keeping the regular theme. In the end, I found out that I was instantiating some views using the Application's context instead of the current's activity context. For some reason, Application's context does not track this kind of information.

So, update your code to use current's activity context instead of the application context.

For reference for other users. Avoid:

ContextCompat.getColor(getApplicationContext(), R.id.myColor)

And use:

// In a Activity
ContextCompat.getColor(this, R.id.myColor)

// In a View
ContextCompat.getColor(getContext(), R.id.myColor)

// In a Fragment (check against null because getContext can trigger a NPE
Context context = getContext()
if (context != null) {
    ContextCompat.getColor(context, R.id.myColor)
}
like image 153
W0rmH0le Avatar answered Oct 24 '22 03:10

W0rmH0le


I faced similar question as you, and I also found the core of the issue is that Application don't have a theme wrapper like Activity. so W0rmH0le's answer can resolve this problem. but for me, there are lots of code can't fetch an activity or view's context. so I thought why don't we create one singleton Context wrapper nightMode theme. there is the code, and it works fine for me.

Resources res = getApplication().getResources();
Configuration configuration = new Configuration(res.getConfiguration());
int nightNode = AppCompatDelegate.getDefaultNightMode();
if (nightNode == AppCompatDelegate.MODE_NIGHT_NO) {
    configuration.uiMode = Configuration.UI_MODE_NIGHT_NO | (res.getConfiguration().uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
} else if (nightNode == AppCompatDelegate.MODE_NIGHT_YES) {
    configuration.uiMode = Configuration.UI_MODE_NIGHT_YES| (res.getConfiguration().uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
} else {
    configuration.uiMode = res.getConfiguration().uiMode;
}
mThemeContext = getApplication().createConfigurationContext(configuration);

then you can use mThemeContext replace Application, and you can found the right color followed night mode.

like image 35
xh jia Avatar answered Oct 24 '22 04:10

xh jia


Application context don't know anything about current theme or day/night so if you get a resource from application context, you will get the resource in default app theme. A solution to this problem is to use activity/fragment context but in some situations you may not have activity or fragment and only have application context. I have created a context wrapper class to add Day & Night theme to Application context:

import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat

class ThemedContext(application: Context) {

    private val themedContext: Context

    init {
        val res: Resources = application.resources
        val configuration = Configuration(res.configuration)
        val filter = res.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK.inv()

        configuration.uiMode = when (AppCompatDelegate.getDefaultNightMode()) {
            AppCompatDelegate.MODE_NIGHT_NO -> Configuration.UI_MODE_NIGHT_NO or filter
            AppCompatDelegate.MODE_NIGHT_YES -> Configuration.UI_MODE_NIGHT_YES or filter
            else -> res.configuration.uiMode
        }

        themedContext = application.createConfigurationContext(configuration)
    }

    fun getColor(@ColorRes id: Int) = ContextCompat.getColor(themedContext, id)
    fun getDrawable(@DrawableRes id: Int) = ContextCompat.getDrawable(themedContext, id)
    //todo Add other getter methods as needed...
}

This code has been tested and worked.

like image 2
ygngy Avatar answered Oct 24 '22 02:10

ygngy