システムを対象としたNight Modeの切り替えは、SettingsアプリとUiModeManagerで行う方法がります。
ここでは、UiModeManagerを使った方法を説明します。
※Night Modeの切り替えの全体像は「Dark ThemeとNight Modeの関係」を参照
※環境:Android Studio Jellyfish | 2023.3.1
Kotlin 1.9.0
Compose Compiler 1.5.1
androidx.compose.foundation:foundation 1.5.0
切り替えの変遷
Night Modeは古く(API8)からあるモードです。
API28までは、UiModeManagerを使って、ユーザのプログラムから制御可能なシステムのパラメータでした。
API29からは、「Dark Theme」機能と連動し、ユーザのプログラムから制御不可能なシステムのパラメータになりました。
代わりにSettingsアプリから制御します。

API≧29でプログラムから切り替えを行いたければ、システムのパラメータに対するアクセスの許可が必要になります。
※アクセスの許可:MODIFY_DAY_NIGHT_MODEパーミッション
しかし、この許可の取得はユーザのプログラムに許されていません。ですので、実際のところ、切り替えは不可能です。
切り替えの方法
アプリケーションフレームワークのUiModeManager経由で行います。
val _uiModeMgr = getSystemService(UI_MODE_SERVICE) as UiModeManager
// YES/NO
_uiModeMgr.setNightMode(UiModeManager.MODE_NIGHT_NO
_uiModeMgr.setNightMode(UiModeManager.MODE_NIGHT_YES
// CUSTOM(指定は不可能)
_uiModeMgr.setNightMode(UiModeManager.MODE_NIGHT_CUSTOM) // API≧30
_uiModeMgr.customNightModeStart = LocalTime.parse("20:00:00") // アクセス許可が必要
_uiModeMgr.customNightModeEnd = LocalTime.parse("06:00:00") // アクセス許可が必要
// AUTO
_uiModeMgr.setNightMode(UiModeManager.MODE_NIGHT_AUTO
val _uiModeMgr = LocalContext.current.getSystemService(UI_MODE_SERVICE) as UiModeManager
// YES/NO
_uiModeMgr.setNightMode(UiModeManager.MODE_NIGHT_NO
_uiModeMgr.setNightMode(UiModeManager.MODE_NIGHT_YES
// CUSTOM(指定は不可能)
_uiModeMgr.setNightMode(UiModeManager.MODE_NIGHT_CUSTOM) // API≧30
_uiModeMgr.customNightModeStart = LocalTime.parse("20:00:00") // アクセス許可が必要
_uiModeMgr.customNightModeEnd = LocalTime.parse("06:00:00") // アクセス許可が必要
// AUTO
_uiModeMgr.setNightMode(UiModeManager.MODE_NIGHT_AUTO
切り替えのタイプ
指定できる切り替えのタイプは3つです。
| 切り替えのタイプ(UiModeManager.XXX) | 結果 | |||
|---|---|---|---|---|
| 手動 | MODE_NIGHT_YES | スイッチ | Dark On | Night |
| MODE_NIGHT_NO | Dark Off | Day | ||
| 自動 | MODE_NIGHT_AUTO | 暦に従う | 日の入時刻 | Day⇒Night |
| 日の出時刻 | Night⇒Day | |||
| MODE_NIGHT_CUSTOM ※API≧30 | 指定時刻 | Start時刻 | Day⇒Night | |
| End時刻 | Night⇒Day | |||
| ※isSystemInDarkTheme()はUiModeManagerに切り替えられた結果を返す true:Night Mode、false:Day Mode |
||||
MODE_NIGHT_CUSTOMはAPI≧30の場合に使用できます。ですので、指定は不可能です。
タイプ:YES/NO
手動で行われるOn/Offのスイッチ動作をします。
YESの時刻でDay⇒Nightへ、NOの時刻でNight⇒Dayへ遷移します。

タイプ:AUTO
現在地の「日の入」/「日の出」時刻に切り替えを行います。
Android端末は常に自身の地図上の位置を把握するように努めています。位置はGPSやネットワーク、電話のアクセスポイントなどから得ます。現在地は最後に確定した地図上の位置です。
現在地の確認は、Google Mapアプリを開き、現在値ボタンを押下すれば分かります。地図の中心が現在地です。

日の入(Sunset)の時刻でDay⇒Nightへ、日の出(Sunrise)の時刻でNight⇒Dayへ遷移します。

切り替えの反映
切り替えは直ちに反映されます。
Settingsアプリで行った場合と異なるので注意してください。。

切り替えは構成の変更を伴って行われます。ですので、Activityは再作成されます。
構成の変更
NightとDay Modeの切り替えが行われると、構成の変更が発生し、Activityが再作成されます。これにより、DarkとLight Themeが切り替わります。
Activityの再作成を行いたくなければ、Manifestファイルへ下記のように追記してください。
Activityの再作成は抑制され、onConfigurationChanged()がコールパックされます。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application ...>
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.DarkMode"
android:configChanges="uiMode">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
class MainActivity : ComponentActivity() {
...
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
val _nightMode = newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK
when(_nightMode) {
Configuration.UI_MODE_NIGHT_NO -> {/* 処理 */} // Night Mode
Configuration.UI_MODE_NIGHT_YES -> {/* 処理 */} // Day Mode
Configuration.UI_MODE_NIGHT_UNDEFINED -> {/* 処理 */}
else -> {}
}
}
...
}
Jetpack Composeにおいて、Activityを再作成しない場合でも、LightとDark Themeが切り替わります。
これは、isSystemInDarkTheme()の出力が変わることで、Composable関数(UI要素)の状態が変化し、再Composeが行われるからです。
付録:エミュレータで現在地の設定
エミュレータで現在地の設定を行う場合は、Extended Controlsパネルを使います。
パネルを開きます。

地図を目的の場所へ移動させて、ダブルクリックで場所を決定します。

“Set Location”の押下で、現在地として登録されます。
関連記事:
