最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Jetpack Compose: WebView Dark Mode? - Stack Overflow

programmeradmin1浏览0评论

How do I have WebView use the dark mode in Jetpack Compose (minSdk=33, compileSdk=35)?

What I get is a WebView that is black-on-white even if everything else is white-on-black; the same kind of misbehavior happens on emulated devices with API 33, 34, 35:

I know that this question was asked and solved many times, even when Jetpack Compose did not exist. I found examples, they must have worked with views or with pre-33 Jetpack Compose. I tried a number of suggestions (commented out in the code below), nothing seems to work, even all of them together. I expected WebView night mode to work out of the box. What am I missing?

There have been reports that the problem was with the theme, but either I cannot figure out what they did, or it does not work for me, or it does not work anymore. Or maybe there are problems with the material theme.

Probably I misunderstand "if the web content uses prefers-color-scheme and the content author allows it" from the docs, what does it mean "the content author allows it", how do I allow it in my html? By default, does "the content author allow it"?

What I want in general is to have 3rd party web pages in the dark mode.

This is my MRE application. It uses a custom Material Design theme (generated online from the leftmost image, with the two topmost fonts).

MainActivity.kt:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            MyAppTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    MainLayout(
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }
}

@Composable
fun MainLayout(modifier: Modifier = Modifier) {
    val myContext = LocalContext.current
    val myWebView = remember { makeWebView(myContext) }

    Column(modifier = modifier) {
        Surface {
            Column {
                Text("first line", color = MaterialTheme.colorScheme.primary)
                Text("second line", color = MaterialTheme.colorScheme.secondary)
                AndroidView(
                    modifier = Modifier.weight(1f),
                    factory = { myWebView },
                )
                Text("third line", color = MaterialTheme.colorScheme.tertiary)
            }
        }
    }
}

val htmlText =
    "<html> <head>" +
            // does not help
            //"<meta name=\"color-scheme\" content=\"light dark\" />" +
            "</head><body> <h1>Sample Document</h1> <h2>Level 2 Header</h2>" +
            "Foo bar baar baz qux quux <p/> Foo bar baar baz qux quux" +
            "</body></html>"

fun makeWebView(
    context: Context,
): WebView {
    return WebView(context).apply {
        Log.d("~~~~~~", "Created WebView: ${this.hashCode().toHexString()}")
        //any of these two can fix "the whole screen is blank" error (intermittent)
        setLayerType(View.LAYER_TYPE_SOFTWARE, null)
        //setLayerType(View.LAYER_TYPE_HARDWARE, null)

        // Black on dark if uncommented
        //setBackgroundColor(android.graphics.Color.TRANSPARENT)

        // there's nothing about the foreground color

        settings.javaScriptEnabled = true
        settings.loadWithOverviewMode = true
        settings.useWideViewPort = true
        settings.setSupportZoom(true)
        // does not help
        //settings.isAlgorithmicDarkeningAllowed = true
        // does not help
        //settings.forceDark = WebSettings.FORCE_DARK_ON
        // does not help
        //isForceDarkAllowed = true

        loadData(htmlText, "", "")
    }
}

themes.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="Theme.DarkrWebView" parent="android:Theme.Material.Light.NoActionBar"/>
</resources>

build.gradle.kts:

...
android { ...
    compileSdk = 35

    defaultConfig { ...
    minSdk = 33
    targetSdk = 35
...

UPDATE I could enable the dark theme and get black-on-dark (magnified so that you probably can mention the black letters, if you are lucky; if there's a problem, you may try to look at you screen from different angles):

by adding:

build.gradle.kts:

implementation(libs.androidx.webkit)

gradle/libs.versions.toml:

[versions]
webkit = "1.12.1"

[libraries]
androidx-webkit = { group = "androidx.webkit", name = "webkit", version.ref = "webkit" }

Unfortunately, this black-on-black is unreadable, and I have not managed to get white-on-black.

How do I have WebView use the dark mode in Jetpack Compose (minSdk=33, compileSdk=35)?

What I get is a WebView that is black-on-white even if everything else is white-on-black; the same kind of misbehavior happens on emulated devices with API 33, 34, 35:

I know that this question was asked and solved many times, even when Jetpack Compose did not exist. I found examples, they must have worked with views or with pre-33 Jetpack Compose. I tried a number of suggestions (commented out in the code below), nothing seems to work, even all of them together. I expected WebView night mode to work out of the box. What am I missing?

There have been reports that the problem was with the theme, but either I cannot figure out what they did, or it does not work for me, or it does not work anymore. Or maybe there are problems with the material theme.

Probably I misunderstand "if the web content uses prefers-color-scheme and the content author allows it" from the docs, what does it mean "the content author allows it", how do I allow it in my html? By default, does "the content author allow it"?

What I want in general is to have 3rd party web pages in the dark mode.

This is my MRE application. It uses a custom Material Design theme (generated online from the leftmost image, with the two topmost fonts).

MainActivity.kt:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            MyAppTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    MainLayout(
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }
}

@Composable
fun MainLayout(modifier: Modifier = Modifier) {
    val myContext = LocalContext.current
    val myWebView = remember { makeWebView(myContext) }

    Column(modifier = modifier) {
        Surface {
            Column {
                Text("first line", color = MaterialTheme.colorScheme.primary)
                Text("second line", color = MaterialTheme.colorScheme.secondary)
                AndroidView(
                    modifier = Modifier.weight(1f),
                    factory = { myWebView },
                )
                Text("third line", color = MaterialTheme.colorScheme.tertiary)
            }
        }
    }
}

val htmlText =
    "<html> <head>" +
            // does not help
            //"<meta name=\"color-scheme\" content=\"light dark\" />" +
            "</head><body> <h1>Sample Document</h1> <h2>Level 2 Header</h2>" +
            "Foo bar baar baz qux quux <p/> Foo bar baar baz qux quux" +
            "</body></html>"

fun makeWebView(
    context: Context,
): WebView {
    return WebView(context).apply {
        Log.d("~~~~~~", "Created WebView: ${this.hashCode().toHexString()}")
        //any of these two can fix "the whole screen is blank" error (intermittent)
        setLayerType(View.LAYER_TYPE_SOFTWARE, null)
        //setLayerType(View.LAYER_TYPE_HARDWARE, null)

        // Black on dark if uncommented
        //setBackgroundColor(android.graphics.Color.TRANSPARENT)

        // there's nothing about the foreground color

        settings.javaScriptEnabled = true
        settings.loadWithOverviewMode = true
        settings.useWideViewPort = true
        settings.setSupportZoom(true)
        // does not help
        //settings.isAlgorithmicDarkeningAllowed = true
        // does not help
        //settings.forceDark = WebSettings.FORCE_DARK_ON
        // does not help
        //isForceDarkAllowed = true

        loadData(htmlText, "", "")
    }
}

themes.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="Theme.DarkrWebView" parent="android:Theme.Material.Light.NoActionBar"/>
</resources>

build.gradle.kts:

...
android { ...
    compileSdk = 35

    defaultConfig { ...
    minSdk = 33
    targetSdk = 35
...

UPDATE I could enable the dark theme and get black-on-dark (magnified so that you probably can mention the black letters, if you are lucky; if there's a problem, you may try to look at you screen from different angles):

by adding:

build.gradle.kts:

implementation(libs.androidx.webkit)

gradle/libs.versions.toml:

[versions]
webkit = "1.12.1"

[libraries]
androidx-webkit = { group = "androidx.webkit", name = "webkit", version.ref = "webkit" }

Unfortunately, this black-on-black is unreadable, and I have not managed to get white-on-black.

Share Improve this question edited Mar 8 at 21:16 18446744073709551615 asked Mar 6 at 22:42 1844674407370955161518446744073709551615 16.9k4 gold badges101 silver badges132 bronze badges 1
  • You need to create Interface which will interact with JavaScript so when you change Theme in android it will also send signal to JavaScript. – Chirag Thummar Commented Mar 7 at 6:43
Add a comment  | 

1 Answer 1

Reset to default 0

The algorithmic darkening in WebView (Android API level 33, 34, 35) is controlled by two flags:

settings.isAlgorithmicDarkeningAllowed = true

in the code, and

<item name="android:isLightTheme">false</item>

in the theme.

The second flag controls whether your WebView is in the dark mode. (With Jetpack Compose, you may have your dark mode even without this flag, but if you use a WebView, you need it.)

You absolutely do need two themes: if you set this flag for the only theme, your WebView will always be in the dark mode.

Do not use transparent background to fix the WebView night mode problem. If you have only one theme and do setBackgroundColor(Color.TRANSPARENT), as someone suggested here on SO, then without isLightTheme==false, you get black-on-black in the dark mode; with isLightTheme==false, you get white-on-white in the light mode. So you must specify two themes, one in values/, and one in values-night/.

You do NOT need a dependency on WebKit, the out-of-the-box WebView works.

Even if you explicitly specify colors for the dark theme of your html content, these colors still may be changed by algorithmic darkening. See discussion in the end of this post.

The value for android:configChanges that controls activity re-creation when the user switches between light and dark modes is uiMode (see the code in this post).

(Although I used google here, you will not be able to use google search from an Android app, you will need magic tokens, otherwise your query will not work, even the hl=en parameter will not be respected.)

For the record, the fastest and most convenient way to switch between dark and light modes is to have a dedicated terminal window with

$ adb shell "cmd uimode night yes"
Night mode: yes
$ adb shell "cmd uimode night no"
Night mode: no
$ adb shell "cmd uimode night yes"
Night mode: yes
$ adb shell "cmd uimode night no"
Night mode: no
$ 

The complete example code is given below. Note that in Main.kt there are comments about what is working, and what is not. The material design theme (not shown here) was generated online with Material Theme Builder, from the leftmost image and two topmost fonts.

.../app/src/main/res$ tree .
...
├── values
│   ├── colors.xml
│   ├── font_certs.xml
│   ├── strings.xml
│   └── themes.xml
├── values-night
│   └── themes.xml
...

values/themes.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="Theme.DarkWebView" parent="android:Theme.Material.Light.NoActionBar"/>
</resources>

There is no isLightTheme flag above. Note that other dark-and-light themes may have this flag preset to false, so you may wish to explicitly set it to true for your light theme. It is also possible to use a theme with isLightTheme missing or true for the light mode, and some other theme with isLightTheme preset to false for the dark mode (and nobody gonna guess that the important thing is this flag).

values-night/themes.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="Theme.DarkWebView" parent="android:Theme.Material.Light.NoActionBar">
        <item name="android:isLightTheme">false</item>
    </style>
</resources>

Main.kt:

@file:OptIn(ExperimentalStdlibApi::class)

package com.example.darkwebview

import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.View
import android.webkit.WebView
import androidx.activity.ComponentActivity
import androidx.activitypose.setContent
import androidx.activity.enableEdgeToEdge
import androidxpose.foundation.layout.Column
import androidxpose.foundation.layout.fillMaxSize
import androidxpose.foundation.layout.padding
import androidxpose.material3.MaterialTheme
import androidxpose.material3.Scaffold
import androidxpose.material3.Surface
import androidxpose.material3.Text
import androidxpose.runtime.Composable
import androidxpose.runtime.remember
import androidxpose.ui.Modifier
import androidxpose.ui.platform.LocalContext
import androidxpose.ui.viewinterop.AndroidView
//import androidx.webkit.WebSettingsCompat
//import androidx.webkit.WebViewCompat
//import androidx.webkit.WebViewFeature
import com.example.darkwebview.ui.theme.MyAppTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            MyAppTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    MainLayout(
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }
}

@Composable
fun MainLayout(modifier: Modifier = Modifier) {
    val myContext = LocalContext.current
    val myWebView = remember { makeWebView(myContext) }

    Column(modifier = modifier) {
        Surface {
            Column {
                Text("first line", color = MaterialTheme.colorScheme.primary)
                Text("second line", color = MaterialTheme.colorScheme.secondary)
                AndroidView(
                    modifier = Modifier.weight(1f),
                    factory = { myWebView },
                )
                Text("third line", color = MaterialTheme.colorScheme.tertiary)
            }
        }
    }
}

// does not help to make webView use the night theme;
// works only after adding  values-night/themes.xml
// The colors get modified, so that `background: white; and `color: black;` give a white text on a black background.
// In this example, the light mode is black-on-white, but the dark mode is something like
// light purple on dark teal rather than, as specified, dark purple on light blue.
val css = """
        @media (prefers-color-scheme: dark) {
        body {
            background: powderblue;
            color: rebeccapurple;
        }
    }
    """

val htmlText =
            "<html> <head>" +
            // works only after adding values-night/themes.xml
            //"<style>\n$css\n</style>" +
            "</head><body> <h1>Sample Document</h1> <h2>Level 2 Header</h2>" +
            "Foo bar baar baz qux quux <p/> Foo bar baar baz qux quux" +
            "</body></html>"

fun makeWebView(
    context: Context,
): WebView {
    return WebView(context).apply {
        Log.d("~~~~~~", "Created WebView: ${this.hashCode().toHexString()}")
        //any of these two can fix "the whole screen is blank" error
        setLayerType(View.LAYER_TYPE_SOFTWARE, null)
        //setLayerType(View.LAYER_TYPE_HARDWARE, null)

        val jsObjName = "jsObject"

        settings.javaScriptEnabled = true
        settings.loadWithOverviewMode = true
        settings.useWideViewPort = true
        settings.setSupportZoom(true)
        // works after adding values-night/themes.xml with <item name="android:isLightTheme">false</item>
        settings.isAlgorithmicDarkeningAllowed = true

        // works, but requires a dependency on webkit
        // also requires values-night/themes.xml with <item name="android:isLightTheme">false</item>
//        if(androidx.webkit.WebViewFeature.isFeatureSupported(androidx.webkit.WebViewFeature.ALGORITHMIC_DARKENING)) {
//            WebSettingsCompat.setAlgorithmicDarkeningAllowed(settings, true);
//        }

        // both work
//        loadData(htmlText, "", "")
        loadUrl("https://www.google/")
    }
}

AndroidManifest.xml (Note: uiMode):

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android/apk/res/android"
    xmlns:tools="http://schemas.android/tools">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.DarkWebView"
        tools:targetApi="33">
        <activity
            android:name="com.example.darkwebview.MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:configChanges="screenLayout|orientation|screenSize|keyboard|keyboardHidden|smallestScreenSize|uiMode"
            android:theme="@style/Theme.DarkWebView">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Colors:

The following image shows how the colors change when algorithmic darkening is used. The code is given above, see val css. There, we have a css written to work only in the dark mode. It may sound counter-intuitive, but the colors specified in that css are still subject to automatic darkening. (The rightmost screenshot in the image below is obtained in the light mode after removing the prefers-color-scheme thing.)

One more example: if the css specifies background: white; and color: black;` the result is a white text on a black background, which is exactly opposite of what css says.

发布评论

评论列表(0)

  1. 暂无评论