Letting Webview on Android Work with Prefers-Color-Scheme: Dark

Letting WebView on Android work with prefers-color-scheme: dark

Android Webview 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
}

How to force dark web pages in android webview?

If you see androidx.webkit:webkit:1.3.0-beta01 change logs you could see ForceDark API added to control if WebView should be rendered in dark mode. You can use ForceDarkStrategy API to control WebView darkening (CSS/web content darkening versus auto darkening).

Prior try to access this feature ensure that it is supported by the Webview is being used in. For this, the WebViewFeature class have an isFeatureSupported() function, which can be used to check if a given feature is supported. So before we go ahead and set the support for dark mode check if it is supported:

if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { ... }

There are 3 different constants available in WebSettingsCompat to configure -

  • FORCE_DARK_OFF – Disable the force dark mode for the webview, meaning the content of the webview will be rendered as-is
  • FORCE_DARK_ON – Enable the force dark mode for the webview, meaning the content of the webview will always be rendered with a dark theme
  • FORCE_DARK_AUTO – Enable the force dark mode for the webview depending on the state of the parent view, meaning that the system dark mode setting will be followed when rendering the content of the webview.

Apply it accordingly using setForceDark function-

WebSettingsCompat.setForceDark(webView.settings, WebSettingsCompat.FORCE_DARK_ON)

How do I force darken the WebView parent so that I can use WebSettingsCompat.FORCE_DARK_AUTO?

Android Force Dark and Android DayNight themes are two different features.

  • Force Dark is meant to automatically turn "light-only" apps into dark mode apps by the system. It's meant as a quick fix for light-only apps to "support" dark mode. If your app is not light-only and already supports DayNight/Dark mode you should not (and basically cannot) use this feature.

  • Android DayNight themes are hybrid themes that automatically switch between their light and dark version based on the system theme or day/night mode settings. This is the recommended way to implement dark mode.

For some unexplained reason the WebKit/WebView developers decided to link the webview's FORCE_DARK_AUTO setting to the Android Force Dark feature but not to the recommended DayNight feature

As you stated your app is using a DayNight theme. Therefore the Android 10 Force Dark feature is disabled in your app and the FORCE_DARK_AUTO setting on your webview doesn't work.

So to enable automatic dark mode switching for your webview, you need to check in what mode your DayNight theme is running and then enable or disable the dark mode on the webview accordingly:

if(WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {

int nightModeFlags = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;

if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES) {
//Theme is switched to Night/Dark mode, turn on webview darkening
WebSettingsCompat.setForceDark(myWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
}
else{
//Theme is not switched to Night/Dark mode, turn off webview darkening
WebSettingsCompat.setForceDark(myWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
}
}

Although not recommended, if you still want to drop your DayNight theme support and enable the Force Dark quick fix instead you can read some general info on Android 10's Force Dark feature below.

Android 10 Force Dark feature

Force Dark for Android is a feature introduced in Android 10 (API 29). It allows any app or activity that's using a light-only theme and therefore does not support dark mode by itself to automatically be darkened by the system when the device is running in dark mode.

Since you have no control over how this feature applies dark mode to your activities and components, it's only recommended to use if you need a "quick fix" to implement dark mode. If you want to implement dark mode properly in your app, you need to either use a dark theme or a auto-switching DayNight theme.

To enable force dark for your application it needs to:

  • Use or inherit from a light-only theme (<item name="isLightTheme">true</item> needs to be set for the theme or its ancestor)
  • <item name="android:forceDarkAllowed">true</item> needs to be set for your theme or its ancestor.
  • You need to have your compileSdkVersion (and possibly targetSdkVersion) in your build.gradle set to at least 29

Most system and AndroidX light-only themes (like android:Theme.Material.Light or Theme.MaterialComponents.Light) should have the light theme property set by default, so creating a theme inheriting from them like this should enable force dark:

<style name="AppTheme" parent="Theme.MaterialComponents.Light">
<!-- Other properties here-->

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

And then apply that theme to your application (or a single activity) in the Android manifest

<application
...
android:theme="@style/AppTheme">
</application>

This will enable force dark for the entire application or activity you have set the theme on. All views within it have force dark enabled by default now. You can opt-out individual views from force dark by setting android:forceDarkAllowed="false" on them.

Dark mode switch with prefers-color-scheme: dark

An easy way would be to use CSS variables, so you wont' have to load a whole new css file altogether and it's slick

 :root {
--primary-color: #302AE6;
--secondary-color: #536390;
--font-color: #424242;
--bg-color: #fff;
--heading-color: #292922;
}

[data-theme="dark"] {
--primary-color: #9A97F3;
--secondary-color: #818cab;
--font-color: #e1e1ff;
--bg-color: #161625;
--heading-color: #818cab;
}

body {
background-color: var(--bg-color);
color: var(--font-color);
width: 100%;
}

h1 {
color: var(--heading-color);
font-family: "Sansita One", serif;
font-size: 2rem;
margin-bottom: 1vh;
}

and with JS you can toggle that

const chk = document.getElementById('chk');

chk.addEventListener('change', (e) => {
if (e.target.checked) {
document.documentElement.setAttribute('data-theme', 'dark');
}
else {
document.documentElement.setAttribute('data-theme', 'light');
}
});



Related Topics



Leave a reply



Submit