6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Android】ダークテーマを反映するアプリ起動時の適切なタイミングについて【Kotlin】

Last updated at Posted at 2020-04-20

Android 10 で公式にサポートされた「ダークテーマ(ナイトモード)」ですが、スレッド式メモ帳アプリ『CBnotes』では、Android 10 未満の端末にインストールしても「ダークテーマ(ナイトモード)」の切り替え設定が可能になっています。

以下の公式ガイドに従って、アプリを DayNight テーマから継承すれば、簡単に「ダークテーマ(ナイトモード)」を実現できました。『CBnotes』では、「MaterialComponents のダークテーマ機能」を使用しています。

Dark theme | Android Developers
Dark Theme - Material Components for Android

実装の重要な注意点

ところが、公式ガイドには記載されていない重要なポイントがありました。

公式ガイドに従って「AppCompatDelegate.setDefaultNightMode()」を呼び出してダークテーマ(ナイトモード)を設定しますが、なんと実はこの設定値はシステムに保存されません

MODE_NIGHT_YES」を設定してダークテーマ(ナイトモード)に切り替えていても、次にアプリを起動すると、ライトテーマに戻ってしまいます

ですので、やや面倒ですが、独自にアプリ内で設定を保存しておかなければならず、また、アプリの起動時にその設定を反映しなければなりません。

その設定の反映は、タイミングが非常に重要です。

公式ガイドに記載がある通り(以下)、設定の切り替えによってアクティビティが再生成されますから、アクティビティの onCreate で反映するとタイミングが遅すぎて、アクティビティが二度連続で起動するような挙動(見た目にチラツキが分かる)になってしまいます。

注:AppCompat v1.1.0 以降、開始されているアクティビティは setDefaultNightMode() により自動的に再作成されます。

アプリが起動するときの、最速の処理タイミングは、Application の onCreate です。「ダークテーマ(ナイトモード)」の反映は、このタイミングで対応する必要があります。

CBnotes』における実装

Application の実装は以下の通りです。この onCreate のタイミングで実装すれば、反映はスムーズで、見た目(チラツキ)の問題も起こりません。設定の保存は小規模なので SharedPreferences で十分です。

package cutboss.cbnotes

import android.app.Application
import android.os.Build
import androidx.preference.PreferenceManager
import androidx.appcompat.app.AppCompatDelegate

class BossApplication : Application() {

    companion object {
        private const val TAG: String = "BossApplication"

        private const val KEY_DARK_THEME = "dark_theme"
        private const val KEY_MODE_NIGHT = "mode_night"
    }

    override fun onCreate() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            // Sets the default night mode.
            setDefaultNightMode(getDarkTheme())
        } else {
            // Sets the default night mode.
            setDefaultNightMode(getNightMode())
        }

        // Called when the application is starting, before any activity, service,
        // or receiver objects (excluding content providers) have been created.
        super.onCreate()
    }

    /**
     * Sets the default night mode.
     *
     * @param isChecked The new checked state of buttonView.
     */
    fun setDefaultNightMode(isChecked: Boolean) {
        // Set a boolean value in the preferences.
        putDarkTheme(isChecked)

        // Sets the default night mode.
        AppCompatDelegate.setDefaultNightMode(
            if (isChecked) {
                // Night mode which uses always uses a dark mode, enabling night qualified
                // resources regardless of the time.
                AppCompatDelegate.MODE_NIGHT_YES
            } else {
                // Night mode which uses always uses a light mode, enabling notnight qualified
                // resources regardless of the time.
                AppCompatDelegate.MODE_NIGHT_NO
            }
        )
    }

    /**
     * Sets the default night mode.
     *
     * @param mode The night mode.
     */
    fun setDefaultNightMode(@AppCompatDelegate.NightMode mode: Int) {
        // Set an int value in the preferences.
        putNightMode(mode)

        // Sets the default night mode.
        AppCompatDelegate.setDefaultNightMode(mode)
    }

    /**
     * Retrieve a boolean value from the preferences.
     *
     * @return Returns the preference value if it exists, or defValue.
     */
    fun getDarkTheme(): Boolean {
        return PreferenceManager
            .getDefaultSharedPreferences(this)
            .getBoolean(KEY_DARK_THEME, false)
    }

    /**
     * Set a boolean value in the preferences.
     *
     * @param value The new value for the preference.
     */
    private fun putDarkTheme(value: Boolean) {
        PreferenceManager
            .getDefaultSharedPreferences(this)
            .edit()
            .putBoolean(KEY_DARK_THEME, value)
            .apply()
    }

    /**
     * Retrieve an int value from the preferences.
     *
     * @return Returns the preference value if it exists, or defValue.
     */
    fun getNightMode(): Int {
        return PreferenceManager
            .getDefaultSharedPreferences(this)
            .getInt(KEY_MODE_NIGHT, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
    }

    /**
     * Set an int value in the preferences.
     *
     * @param value The new value for the preference.
     */
    private fun putNightMode(value: Int) {
        PreferenceManager
            .getDefaultSharedPreferences(this)
            .edit()
            .putInt(KEY_MODE_NIGHT, value)
            .apply()
    }

}

元記事(note

Android 10 未満でもダークテーマ(ナイトモード)に対応する方法|Kotlin|開発裏話

CBnotes』ソースコード一式

以下 note では、実際に Google Play へ公開リリースしている『CBnotes』のソースコード一式を公開しております。

Android(Kotlin)アプリ開発を学習している方々には「参考教材」として、業務で動作実績のあるサンプルコード&開発環境一式が必要なプロの方々には「工数削減」として、大変にオススメです。

スレッド式メモ帳アプリ Android(Kotlin)ソースコード一式を公開
https://note.com/cbnotes/n/n0ee15a0562a2

参考記事

アプリにDayNightテーマを追加する
DayNight — Adding a dark theme to your app
AppCompatDelegate.setDefaultNightMode() picked up by main activity only the first time?
Androidアプリでダークテーマを実装する
DarkTheme対応
これからAndroidでダークモードを実装するエンジニア/デザイナ向け資料

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?