38

I have an Android App that uses webview, and lately I'm trying to figure out how to add a dark theme by using the new @media (prefers-color-scheme: dark) CSS syntax. I have the correct CSS written on my page, and if I open it in Chrome with the dark mode of Chrome turning on, it works. I also have my AppTheme inheriting Theme.AppCompat.DayNight, and my app shows dark loading dialog etc. when I turn on dark mode for the entire OS on my device. Even the auto-complete options for the <input> elements become dark. But still, the webpage loaded with my webview doesn't turn dark. According to this page, webviews should support this feature, but I just can't get it to work. What am I missing here?

I also just found out that in API 29 there's this WebSettings.setForceDark() method; could it be the thing I'm looking for? I hope to find a solution that works with lower API level though.

By the way, my current workaround is to inject a JavaScript interface like this:

webView.addJavascriptInterface(new JSInterface(), "jsInterface");

...

public class JSInterface {
    @JavascriptInterface
    public boolean isNightMode() {
        int nightModeFlags = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
        return nightModeFlags == Configuration.UI_MODE_NIGHT_YES;
    }
}

And then in my webpage, call the jsInterface.isNightMode() method and dynamically load different CSS file based on the result. It certainly works and responses to the global dark mode setting as desired, but I still wonder if I can make prefers-color-scheme work.

Mu-Tsun Tsai
  • 2,348
  • 1
  • 12
  • 25
  • My dark mode styles don't get applied. I am working with Cordova and I am running an ASP.NET MVC application with the inappbrowser plugin. How can I make dark mode disabled in my cordova application including the in app browser one or make the `prefers-color-scheme: dark` settings in my MVC app to work? – Inês Borges Nov 25 '21 at 12:19
  • I disabled dark mode in my Cordova application including the inappbrowser webview app in MVC by adding `` in all layouts of my webpages in the `` tag. – Inês Borges Nov 25 '21 at 12:28

6 Answers6

58

UPDATE 2022: AndroidX Webkit 1.5.0 fixed a lot of the quirkyness of how webview handles dark mode.

For this to work you need to:

  • Add the latest version of webkit to your project (at least 1.5.0):
    • implementation 'androidx.webkit:webkit:1.5.0'
  • Set the target version of your application to 33 or up
    • targetSdkVersion 33
  • Make sure Android System Webview v100 or up is installed on the device

With this setup the webview will properly adjust based on the app theme without the need to adjust any further settings. The prefers-color-scheme CSS query value will automatically adjust to light or dark based on the type of theme (light/dark) the current activity/fragment is running. (based on the isLightTheme attribute of the theme or its parent themes)

The pre-API 33 methods to adjust dark mode (WebViewSettingsCompat.setForceDark and WebViewSettingsCompat.setForceDarkStrategy etc.) are no-ops if your app targetSdkVersion is set to 33 or up, and can be removed from your code.

NOTE: This new method requires that your app targets API 33 or up, but is backwards compatible back to at least API 29 (the first Android version to support dark mode)

NOTE 2: User-agent darkening (automatic dark mode for web pages that do not support it) is now disabled by default in most cases, but can be enabled using the following method:

if(WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {
    WebSettingsCompat.setAlgorithmicDarkeningAllowed(myWebView.getSettings(), true);
}

Full description of this method's behaviour can be found here




LEGACY METHOD (Apps targeting API 32 or below)

Android Webview on apps targeting API 32 and below handles day/night mode a bit differently from the rest of the views. Setting your theme to dark will change the WebView components (scrollbar, zoom buttons etc.) to a dark mode version, but will not change the content it loaded.

To change the content you need to use the setForceDark method of the webview settings to make it change its contents as well. A compatibility version of this method can be found in the AndroidX webkit package.

Add the following dependency to your gradle build:

implementation 'androidx.webkit:webkit:1.3.0'

(1.3.0 is the minimum required version of this package. But higher versions should work as well.)

And add the following lines of code to your webview intitialization:

if(WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
    WebSettingsCompat.setForceDark(myWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
}

The isFeatureSupported check is there to make sure the Android System WebView version the user has installed on their device supports dark mode (since this can be updated or downgraded independently from the Android version through Google Play).

Note: The setForceDark feature requires Android System WebView v76 or up to be installed on the running device.

The force dark feature for webview content has two so-called strategies:

  • User agent darkening: The webview will set its content to dark mode by automatically inverting or darkening colors of its content.

  • Theme based darkening: The webview will change to dark mode according to the theme of the content (which includes the @media (prefers-color-scheme: dark) query).

To set which strategy the webview should use to apply force dark you can use the following code:

if(WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK_STRATEGY)) {
    WebSettingsCompat.setForceDarkStrategy(myWebView.getSettings(), WebSettingsCompat.DARK_STRATEGY_WEB_THEME_DARKENING_ONLY);
}

Note: Strategy selection requires Android System WebView v83 or up to be installed on the running device. WebView versions that support setForceDark but do not support strategy selections (v76 to v81) will use user agent darkening

The supported strategy options are:

  • DARK_STRATEGY_USER_AGENT_DARKENING_ONLY: Only use user agent darkening and ignore any themes in the content (default)
  • DARK_STRATEGY_WEB_THEME_DARKENING_ONLY: Only use the dark theme of the content itself to set the page to dark mode. If the content doesn't have a dark theme, the webview won't apply any darkening and show it "as is".
  • DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING: Use the dark theme of the content itself to set the page to dark mode. If content doesn't have a dark theme, use user agent darkening.

How do Javascript checks work for darkened webviews?

The JavaScript call window.matchMedia('(prefers-color-scheme: dark)') will match in both the user agent darkening and web theme darkening strategy.

I have my webview set to FORCE_DARK_AUTO and my app is running in a daynight theme, but somehow my webview doesn't apply dark mode automatically based on my app theme. Why does this happen?

It's because the FORCE_DARK_AUTO setting value of the webview doesn't work based on themes (as noted in the documentation). It checks for the Android 10 Force Dark feature (a "quick-fix" dark mode feature for apps. It's similarly named, but not directly related to the WebView force dark).

If you aren't using force dark but a app theme to handle dark mode (as recommended), you have to implement your own check for when to apply the webview's force dark feature. An example when using a DayNight theme:

int nightModeFlags = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES) {
   //Code to enable force dark using FORCE_DARK_ON and select force dark strategy 
}
Leon Lucardie
  • 9,541
  • 4
  • 50
  • 70
  • 6
    Your complete knowledge and expertise on the subject is unbelievable. Although your answer involves an alpha version package, it does work as required. – Mu-Tsun Tsai May 13 '20 at 07:17
  • 1
    @Mu-TsunTsai It's because of some of the features (like the strategy selection) only became available in version 1.3.0 of the androidx webkit package, which (along with version 83 of the Android System Webview mentioned) is still in alpha/beta at the moment of writing. I have added notes on this to the answer and I shall adjust the answer accordingly once the packages go stable. – Leon Lucardie May 13 '20 at 11:10
  • I stumbled upon this, because with the public rollout of v83 of the Android System WebView, our previous approach ( `webSettings.setForceDark(WebSettings.FORCE_DARK_ON);`) stopped working. I noticed both, `setForceDark` and `setForceDarkStrategy` are already available in v 1.2.0 of androidx.webkit, but `setForceDarkStrategy` is marked as `RestrictTo.Scope.LIBRARY_GROUP` so it cannot be called from user-code (and the constants are named differently). I can go around this restriction with a `@SuppressLint` annotation, and it seems to work, but I'm not sure how stable this would be – derpirscher May 29 '20 at 18:38
  • 1
    And as for the version check: If I do `isFeatureSupported` on a v81 webView it returns true for `setForceDark` and false for `setForceDarkStrategy`. If I do the same on v83 it returns true both times. Shouln't that be sufficient, instead of checking for a specific version of the webview component? – derpirscher May 29 '20 at 19:06
  • @derpirscher While v1.2.0 of the webkit package does have the strategy features, they were still experimental at this point so they are restricted. Using them while they are restricted will give issues or sometimes outright rejection of app installation on devices not marked 'userdebug'. See: https://issuetracker.google.com/issues/148330092 and https://source.android.com/setup/develop/new-device – Leon Lucardie May 30 '20 at 05:49
  • @derpirscher As for the version check: When I wrote the answer I still experienced some issues with the `isFeatureSupported` check for `forceDarkStrategy` (especially on v82, which never made it out of beta). Now that v83 is stable the `isFeatureSupported` check seems to work fine, I shall adjust the answer accordingly – Leon Lucardie May 30 '20 at 05:58
  • Thank you for the detailed write up. Does any know some easy sites to test these strategies with? the only thing I can get to work is if I select `FORCE_DARK_ON` and that applies to all pages regardless of strategy. I'm already on WebView 83 so not sure what I'm doing wrong. My app uses the material theme for dark mode and I'm on dark mode at the moment. I'm thinking maybe I'm testing the wrong sites. – casolorz Jul 06 '20 at 17:55
  • @casolorz If you have enabled force dark on the webview with the strategy `DARK_STRATEGY_WEB_THEME_DARKENING_ONLY` it should load pages that do not support theme based dark mode without applying any darkening. You might be testing sites that do support theme based dark mode? You could test with sites like https://www.google.com that do not support theme based dark mode (yet) afaik. But the most consistent way to test this would be loading very simple custom HTML without any theming and see how the different strategies handle it. For example: `Test content`. – Leon Lucardie Jul 06 '20 at 21:37
  • Thank you, this appears to be correct. What is the `FORCE_DARK_AUTO` for then? I had assumed it was for when the app or phone is already on dark mode. – casolorz Jul 07 '20 at 18:59
  • @casolorz FORCE_DARK_AUTO enables the force dark mode of the webview based on the force dark mode of its parent views. Force Dark for views is a new auto-dark feature in Android 10 thats disabled when using a dark app theme: https://developer.android.com/guide/topics/ui/look-and-feel/darktheme#force_dark – Leon Lucardie Jul 08 '20 at 04:43
  • So I set the `WebView` to `FORCE_DARK_AUTO` and ran `AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)` and loaded androidpolice.com and didn't get dark mode. If I set it to `FORCE_DARK_ON` then I do get dark mode. Am I misunderstanding something? I think the ideal situation would be to be able to set `MODE_NIGHT_FOLLOW_SYSTEM` and `FORCE_DARK_AUTO` and have it be dark mode, that way by default it will do what the user probably wants without changing settings, but it doesn't look like it will work that way. – casolorz Jul 08 '20 at 22:04
  • @casolorz To clarify a bit: The Force Dark feature for Android apps/views and the Force Dark feature for webview content are two different (but similar) features that happen to have the same name. The features are generally unrelated except for the WebView setting `FORCE_DARK_AUTO`. This setting makes sure the webview force dark feature is enabled whenever the app/view force dark feature of its parent views is enabled – Leon Lucardie Jul 08 '20 at 23:38
  • @casolorz However, since you are using a DayNight or Dark theme rather than Force Dark to darken your app, the `FORCE_DARK_AUTO` flag won’t work. You’d have to use the night mode check as described in the answer. Hopefully this is changed in the future, but for now it’s the only way. – Leon Lucardie Jul 08 '20 at 23:43
  • I see, I guess I'm confused as to what Force Dark means for an app, I thought it was related to DayNight and `AppCompatDelegate.setDefaultNightMode`. I'll have to do some research as to what that means to see if I can find a way to auto set this up for my dark mode users. – casolorz Jul 09 '20 at 18:18
  • >>"LEGACY METHOD (Apps targeting API 32 or below, or using an older version of AndroidX webkit)"<< Looks like using older version of AndroidX webkit while still targeting API 33 not helps - seems targeting API 33 completely breaking webview's Force Dark support on android 9 and below – Fedir Tsapana Dec 15 '22 at 20:32
  • @FedirTsapana Android 9 does not support (force) dark mode to begin with. It was a feature adeed in Android 10 (API 29). As noted in the answer. – Leon Lucardie Dec 16 '22 at 06:37
  • @LeonLucardie no, I used code WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK) on android 9 devices and it returns true and force darkening worked in years in my app with this code WebSettingsCompat.setForceDark(this, WebSettingsCompat.FORCE_DARK_ON). But after I switched targetSdk to 33 it stops working and my users sent me multiple reports that dark theme on webview stopped working on android 9. You can check this himself - but not use emulator for checking, instead use any device runnig android 9 with updated system webview. – Fedir Tsapana Dec 19 '22 at 09:36
  • @FedirTsapana While certain combinations of webview/device could cause FORCE_DARK_ON to function on Android 9 or lower, it was never officially supported. In target API 33 webview dark mode is based on the `isLightTheme` theme property, so you might be able re-enable it if you add that to your AppCompat theme (be sure to use the AppCompat version of the property `false` and not the system version `false` ). But I'm not sure if it will work on Android 9. – Leon Lucardie Dec 19 '22 at 13:58
  • @LeonLucardie >>But I'm not sure if it will work on Android 9<< unfortunately no – Fedir Tsapana Dec 20 '22 at 20:57
9

What I do, based on your question code, is to force based on current status:

int nightModeFlags = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES) {
  webSettings.setForceDark(WebSettings.FORCE_DARK_ON);
}

This way @media (prefers-color-scheme: dark) works, but @media (prefers-color-scheme: light) still doesn't work (tried using FORCE_DARK_OFF and FORCE_DARK_AUTO in an else)

jcesarmobile
  • 51,328
  • 11
  • 132
  • 176
  • 2
    I inspected the webview and checked the media query matching with `window.matchMedia("(prefers-color-scheme: ...)")` (possible values are `light`, `dark` and `no-preference`). When the mode is initially set to `DARK_ON` it matches the `dark` scheme. Any other mode matches the `no-preference` scheme. Futhermore, once I set mode to `DARK_OFF` or `DARK_AUTO`, setting it to `DARK_ON` later is not correctly propagated to the webview. But the other way around (ie first `DARK_ON` and then `DARK_OFF`) works. – derpirscher Nov 04 '19 at 12:29
  • 2
    To be more precise: The value returned by `getForceDark()` is always the expected value (ie `2` when the last set operation was `DARK_ON` and `0` when it was `DARK_OFF`). But setting `DARK_ON` after `DARK_OFF` or `DARK_AUTO` was set, does not have any visible effect any more. – derpirscher Nov 04 '19 at 12:40
3

There are two relevant pieces of documentation here:

https://developer.android.com/guide/topics/ui/look-and-feel/darktheme#force_dark

https://developer.android.com/reference/android/view/View.html#setForceDarkAllowed(boolean)

The key points are

  1. The WebView must allow force dark, and all its parents must too.
  2. The theme must be marked as light.

This means that getting FORCE_DARK_AUTO behaviour to work in WebView is a little complex. Adding the following to an AppCompat.DayNight theme does work, but is maybe not the best solution as it may apply Android force dark to views other than the WebView.

<item name="android:forceDarkAllowed">true</item>
<item name="android:isLightTheme">true</item>

The other option is to handle this manually, by setting FORCE_DARK_ON/OFF based upon the relevant configuration change.

Currentlly WebView does not support prefers-color-scheme partly because force dark was implemented before prefers-color-scheme standardisation, and partly because there's no way to integrate that sensibly (without style flashes) with force dark. The intent is to provide the ability to choose either force dark or prefers-color-scheme through androidx.webkit.

2
implementation 'androidx.webkit:webkit:1.4.0'


WebSettings settings = webView.getSettings();

if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
   if (isNightMode) {
      WebSettingsCompat.setForceDark(settings, WebSettingsCompat.FORCE_DARK_ON);
   } else {
      WebSettingsCompat.setForceDark(settings, WebSettingsCompat.FORCE_DARK_OFF);
   }
}
Ahamadullah Saikat
  • 4,437
  • 42
  • 39
1

I don't think WebView honours the prefers-color-scheme CSS media query yet.

The new API for setForceDark has three states: on, off or auto.

ON - your content will always be rendered darker everytime.

OFF - your content will always be rendered light everytime.

AUTO - the content will be rendered darker if your app's theme is darker OR the device OS is in dark mode because the user toggles the OS level switch or battery saver mode turned on.

I believe support for older versions of Android and also control over whether to use prefers-color-scheme instead of WebView's force dark is coming soon via AndroidX. Due date is unknown at present.

For now I would recommend setting the WebView to setForceDark Auto. This will work on Android Q and above.

I would keep an eye on AndroidX release notes for the rest of the support you require for devices on Android P and below.

cmd
  • 81
  • 8
  • Unfortunately, it seems that AUTO always resolves to prefers-color-scheme: no-preference even when compiling with target of Android Q running on Android Q. Thus, the webview does not respond to switching the system to darkmode even If the native App does. – derpirscher Nov 04 '19 at 21:00
  • Hi there derpirscher. I am not sure I understand. If you set webview to auto it should follow the Android OS level setting for dark theme. So, if you toggle your android device to dark theme on then webview should force web content to be dark. Is this not the case? – cmd Nov 05 '19 at 00:04
  • 1
    It's not the case for me. I set the app's theme to `AppCompat.DayNight` and I can see the app follows the OS setting for darkmode. But even though I set `websettings.setForceDark(FORCE_DARK_AUTO)` the webview does not go to darkmode when I switch the OS setting. See also my comments to the other answer in this thread – derpirscher Nov 05 '19 at 08:16
  • But maybe, I'm just missing something. What I'm doing in a minimal example: 1) AppTheme = `AppCompat.DayNight`, 2) I have `values/colors.xml` and `values-night/colors.xml`. 3) In `onCreate` I set `AppCompatDelegate.setDefaultNightMode(MODE_NIGHT_FOLLOW_SYSTEM)` and 4) I set `websettings.setForceDark(FORCE_DARK_AUTO)`. I can see the app following dark/light mode switch (statusbar changes colors) but not the webview. It always shows colors like defined under `@media (prefers-color-scheme: no-preference)`, regardless whether there is dark or light mode set. – derpirscher Nov 05 '19 at 09:10
  • @derpirscher how can you check the value of the `prefers-color-scheme` in the webview ? – diAz Mar 11 '20 at 08:37
  • @diAz I have media queries for prefers-color-scheme `dark` `light` and `no-preference` with different colors defined in them and see, which colors are used. – derpirscher Mar 11 '20 at 09:26
  • 1
    @diAz You can also check in javascript with [`window.matchMedia`](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia). For instance `window.matchMedia("(prefers-color-scheme: light)").matches` will return `true`, if `prefers-color-scheme` is set to `light` – derpirscher Mar 11 '20 at 09:29
  • i was expecting this value for the Android webview ^^ – diAz Mar 11 '20 at 16:24
0

for Webview up to v93 (implementation 'androidx.webkit:webkit:1.4.0') the flag DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING wasn't working for me untill I used this code:

if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
            if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK_STRATEGY)){            
            WebSettingsCompat.setForceDarkStrategy(
                webView.settings,
                WebSettingsCompat.DARK_STRATEGY_WEB_THEME_DARKENING_ONLY
            )
        }
        WebSettingsCompat.setForceDark(webView.settings, WebSettingsCompat.FORCE_DARK_ON)
    }    
webView.evaluateJavascript("document.documentElement.setAttribute('data-color-mode', 'dark');", null)

which requests the site's dark theme, but does not force the color inversion.

Xavier Vega
  • 701
  • 5
  • 6