Jim Clermonts
Jim Clermonts

Reputation: 2660

Activity quickly flashes white before switching to dark / night theme

I've implemented a Dark / night mode switch in my App I copied the implementation from the Google I/O App.

I've created a ThemedActivityDelegate that can be included in ViewModels. The actual changing to light or dark is done using the Extension function: updateForTheme.

But I see the SettingsActivity briefly flashing white before it turns black. While I put the updateForTheme method before setContentView.

MainActivity:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
    viewModel = ViewModelProvider(this, viewModelFactory).get(MainViewModel::class.java)
    updateForTheme(viewModel.currentTheme)

    lifecycleScope.launch {
        lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
            launch {
                viewModel.theme.collect { theme ->
                    updateForTheme(theme)
                }
            }
        }
    }
}

Extensions.kt:

fun AppCompatActivity.updateForTheme(theme: Theme) = when (theme) {
    Theme.DARK -> delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES
    Theme.LIGHT -> delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_NO
    Theme.SYSTEM -> delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
    Theme.BATTERY_SAVER -> delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
}

SettingsActivity:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    updateForTheme(Theme.DARK)
    setContentView(R.layout.activity_settings)
}

UPDATE: When I call updateForTheme(viewModel.currentTheme) before super.onCreate(savedInstanceState) then it works but Dagger isn't initialized then.

Upvotes: 7

Views: 1097

Answers (2)

Meet
Meet

Reputation: 950

ThemeChangeActivity.XML

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ThemeChangeActivity">

    <LinearLayout
        android:background="?attr/selectableItemBackground"
        android:id="@+id/themeChangeLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:layout_marginStart="4dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/theme" />

        <TextView
            android:id="@+id/themeStateTxt"
            android:layout_marginStart="4dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="12sp" />
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

ThemeChangeActivity.kt

this activity is theme change for your application

import android.content.SharedPreferences
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatDelegate
import com.google.android.material.dialog.MaterialAlertDialogBuilder

class ThemeChangeActivity : AppCompatActivity() {
    private lateinit var sharedPreferences: SharedPreferences
    private val items = arrayOf("Light", "Dark", "Auto (System Default)")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_theme_change)
        sharedPreferences = getSharedPreferences(getString(R.string.app_name), MODE_PRIVATE)
        themeChangeText(findViewById(R.id.themeStateTxt))
        findViewById<LinearLayout>(R.id.themeChangeLayout).setOnClickListener { themeDialog() }
    }

    private fun themeChangeText(themeStateTxt: TextView) {
        when (sharedPreferences.getInt("night_mode", 2)) {
            0 -> themeStateTxt.text = items[0]
            1 -> themeStateTxt.text = items[1]
            else -> themeStateTxt.text = items[2]
        }
    }

    private fun themeDialog() {
        var checkedItem = sharedPreferences.getInt("night_mode", 2)

        MaterialAlertDialogBuilder(this)
            .setTitle("Theme")
            .setPositiveButton("Ok") { _, _ ->
                when (checkedItem) {
                    0 -> {
                        sharedPreferences.edit().putInt("night_mode", 0).apply()
                        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
                    }
                    1 -> {
                        sharedPreferences.edit().putInt("night_mode", 1).apply()
                        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
                    }
                    else -> {
                        sharedPreferences.edit().putInt("night_mode", 2).apply()
                        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
                    }
                }
                themeChangeText(findViewById(R.id.themeStateTxt))
            }
            .setSingleChoiceItems(items, checkedItem) { _, which ->
                checkedItem = which
            }
            .setCancelable(false)
            .show()
    }
}

MainActivity.Kt

application start check if default theme

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatDelegate

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val sharedPreferences = getSharedPreferences(getString(R.string.app_name), MODE_PRIVATE)
        when (sharedPreferences.getInt("night_mode", 2)) {
            0 -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
            1 -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
            else -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
        }
        setContentView(R.layout.activity_main)
    }
}

AndroidMainfest.xml

app this android:configChanges="uiMode"

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.materialsouk.allcodeapp">
      <application
        android:allowBackup="true"
        android:icon="@mipmap/app_logo"
        android:label="@string/app_name"
        android:requestLegacyExternalStorage="true"
        android:roundIcon="@mipmap/app_logo_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AllCodeApp">
        <activity
            android:name=".ThemeChangeActivity"
            android:exported="false" />
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:configChanges="uiMode">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>

Upvotes: 0

Shlomi Katriel
Shlomi Katriel

Reputation: 2413

Try the following:

fun AppCompatActivity.updateForTheme(theme: Theme) {
    val mode = when (theme) {
        Theme.DARK -> AppCompatDelegate.MODE_NIGHT_YES
        Theme.LIGHT -> AppCompatDelegate.MODE_NIGHT_NO
        Theme.SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
        Theme.BATTERY_SAVER -> AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
        else -> AppCompatDelegate.MODE_NIGHT_UNSPECIFIED
    }
    if (mode != AppCompatDelegate.MODE_NIGHT_UNSPECIFIED) {
        AppCompatDelegate.setDefaultNightMode(mode)
    }
}

Worked well for me in: https://github.com/shlomikatriel/BucksBunny

You may call that setDefaultNightMode on settings while handling user choice.

Upvotes: 1

Related Questions