Android 10 and Dark Mode

While debugging an app on my Pixel XL running Android 10, I noticed that the dark mode is not behaving the same as it does on an iOS device.

I’m using Ionic V6 and when I turn the dark theme on in the device Display settings, the app still displays in the light mode.

When I attempt to debug through the Chrome browser, I can switch the prefers-color-scheme emulation to dark and the app will display in dark mode, will not render that way when opening.

Is there something else that is needed with Android to have the app display in dark mode if the dark theme is selected on the device?

Hi, check this line in your theme/variables.scss
This line changes from the light style to the dark style when the user selects the dark mode on their device.

@media (prefers-color-scheme: dark)

Hey @Carabut - thanks for the reply! Yes. That media query is in my variables.scss file in my project. Additionally, when debugging the Android device through Chrome, I can switch the prefers-color-scheme emulation to dark and the app will appear in dark mode.

Hi,

I have been having issues with Dark mode - I have a couple of apps that have been just fine, until users upgraded Android last week, and complained that the apps were opening in dark mode. I had the line as above in my variables.scss file, as per the docs.

I went through the ionic docs, and stopped using the

@media (prefers-color-scheme: dark)

instead switching to using body.dark and using the manual method of enabling dark mode: Dark Mode | Ionic Documentation

I now have code in app.component.ts which detects whether the system has dark mode set…and I enable it manually IF the user setting is not set to ‘Force off’

Trouble is, I find that this code doesn’t work - meaning the system can be in dark mode, but seemingly never adds the @media (prefers-color-scheme: dark) to (presumably) the webview.

Where I have left it is:

In variables.scss:

body.dark {
...
as per docs

In app.component.ts:

let darkMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
      toggleDarkTheme(darkMediaQuery.matches);

      try {
        // Chrome & Firefox
        darkMediaQuery.addEventListener('change', (e) => {
          toggleDarkTheme(darkMediaQuery.matches);
        });
      } catch (e1) {
        try {
          // Safari
          darkMediaQuery.addListener((e) => {
            toggleDarkTheme(darkMediaQuery.matches);
          });
        } catch (e2) {
          console.error(e2);
        }
      }

      // Add or remove the "dark" class based on if the media query matches
      function toggleDarkTheme(shouldAdd) {
        if (!this.env.global.darkmode || this.env.global.darkmode == 'System settings')
        {
          document.body.classList.toggle('dark', shouldAdd);
        }
      }

This may run when the env.global.darkmode isn’t set…but I don’t care so much. Then, on my homepage init, I do:

  this.env.global.darkmode = this.settingsDoc.doc.darkmode?this.settingsDoc.doc.darkmode:'System settings';
      console.log(this.env.global.darkmode);
      switch(this.env.global.darkmode)
      {
        case 'Force on':
          document.body.classList.toggle('dark', true);
          break;
        case 'Force off':
          document.body.classList.toggle('dark', false);
          break;
        default:
          let darkMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
          document.body.classList.toggle('dark', darkMediaQuery.matches);
          break;
      }

and then, lastly on my settings page I run the same code, based on the (change) event of the user settings,so they see the change straight away…The setting is a dropdown/ion-select with options [‘System settings’, ‘Force on’, ‘Force off’]

This means I think I have correct usage covered, but when the phone is put into dark mode, the app doesn’t pick it up…but I have another test phone that seems to work ok, so there seems to be inconsistencies in

  • Android versions?
  • Phone manufacturer UIs?
  • ionic versions?

Some or all… but…at least I can tell the users that they “have control”, and its not the app that is broken.

Unless someone can tell me any better way of doing all this…!

Regards,

Steve

p.s.

An update - when testing all this with a debug build, the system settings were not adhered to, - i.e. if the phone was set to darkmode, the App did not detect dark mode…

Have just build a prod version, and now, when set to System Settings, the app detects that it is ‘in dark mode’ whether dark mode is set, or not…so there IS a problem somewhere, which has come to light with the Android update, but at least I can tell users to set to “force off” in the app settings…

2 Likes

This was the fix I used to get Dark mode to work correctly in my Android project. In your android/app/src/main/MainActivity.java file make the follow edits:

//This will be the package name of your project
package com.ionic.io;

import android.os.Bundle;
import android.content.res.Configuration;
import android.webkit.WebSettings;

import com.getcapacitor.BridgeActivity;
import com.getcapacitor.Plugin;

public class MainActivity extends BridgeActivity {
    void setDarkMode() {
        // Android "fix" for enabling dark mode
        // @see: https://github.com/ionic-team/capacitor/discussions/1978
        int nightModeFlags = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
        WebSettings webSettings = this.bridge.getWebView().getSettings();
        if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES) {
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
                // As of Android 10, you can simply force the dark mode
                webSettings.setForceDark(WebSettings.FORCE_DARK_ON);
            }
            // Before Android 10, we need to use a CSS class based fallback
            this.bridge.getWebView().evaluateJavascript("document.body.classList.toggle('dark', true);", null);
        } else {
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
                webSettings.setForceDark(WebSettings.FORCE_DARK_OFF);
            }
            this.bridge.getWebView().evaluateJavascript("document.body.classList.toggle('dark', false);", null);
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        setDarkMode();
    }

    @Override
    public void onResume() {
        super.onResume();
        setDarkMode();
    }
}
2 Likes

Now that is perfect!

Thank you - I have complete control over dark mode now, it adheres to the system settings, or the use can override them if they want.

Regards,

Steve

You could also add this to the file if you want to control the Dark/Light mode from the top drawer in Android:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    setDarkMode();
}

There exists a working solution/fix for Cordova:

dont forget to add < preference name=“GradlePluginKotlinEnabled” value=“true” /> to config.xml

If you just need to support prefers-color-scheme, this package works fine and may be easier to manage than manual edits to MainActivity.java, depending on your workflow, etc. You just install it and it does its thing on build. The only peer dependency is Capacitor.

In my app, I have a toggle in the app settings to either use or ignore the system dark mode settings/theme, so I also use @capacitor/device to ensure that I hide that setting in Android 9 and older, since it’s irrelevant (the dark mode/theme was introduced in version 10).

import { Device } from '@capacitor/device';
showDarkModeSettings: boolean = true; // ios and android >= 10
ngOnInit() {
   this.supportsDarkModeSettings();
}
supportsDarkModeSettings = async () => {
    let info = await Device.getInfo();
    if (info.operatingSystem == 'android') {
      let version = info.osVersion.replace(/android/gi, '');
      if (Number(version) < 10) {
        this.showDarkModeSettings = false; // android < 10
      }
    }
};