Search code examples
androidandroid-themeandroid-handlerkotlin-android-extensions

Android Handler Looper Dark Theme behavior


I experienced interesting behavior with Handler(Looper.getMainLooper()). It gets executed twice if my app theme (day/night) is set to different from OS Settings. For example if Dark mode is turned off in device settings and my app MainActivity applies dark theme, then MainActivity starts twice. I did`t find any explanation on why it happens.

SplashActivity very simple

class SplashActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.splash_screen_layout)

        Handler(Looper.getMainLooper()).postDelayed({
            val intent = Intent(this, MainActivity::class.java)
            startActivity(intent)
            Log.i("SPLASH","Main activity started")
            finish()
        }, 2000)
    }

}

Main Activity has the following function to check what theme is saved in app settings and apply it:

Function

private fun checkDarkMode(){
        when (MainSettings(this).darkMode) {
            0 -> {
                AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
                delegate.applyDayNight()
            }
            1 -> {
                AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
                delegate.applyDayNight()
            }
            2 -> {
                AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
                delegate.applyDayNight()
            }
        }
    }

So here if dark mode is turned off in device settings, then handler code will be executed twice for AppCompatDelegate.MODE_NIGHT_YES, for others just once as intended.

It also happens vise versa, if device dark on, then code executes twice for AppCompatDelegate.MODE_NIGHT_NO.

As I said I didn`t find any explanation or solution, so what I did is just defined handler as val and cancelled everything for it in onPause or onDestroy of SplashActivity

private val handler = Handler(Looper.getMainLooper())

override fun onPause() {
        super.onPause()
        handler.removeCallbacksAndMessages(null)
    }

So my question is why it happens and is there another way to avoid it?


Solution

  • I would take a look at the source for AppCompatDelegate which is here (take a lot around line 201 for the part where it recreates the activity).

    I can't offer a direct solution to your problem but an alternative would be to just inherit from one of the DayNight themes (although you do lose the ability to have a night theme on anything below Android 10, but in my opinion it makes things a lot easier).

    Edit: You might be able to perform your checkDarkMode method in an Application class, this should hopefully set the correct mode before any activites are created (thus avoiding them being created again when it changes).

    public class MyApplication extends Application {
    
    public void onCreate() {
        super.onCreate();
        checkDarkMode()
    }