React NativeでDark Modeを手っ取り早く再現する

最近使っている人もちらほら増えてきたダークモードですが、React Nativeでもしっかり対応できます。UI側の実装なので最初は気が乗らかったのですが、完成させてみると意外に気に入るかも知れません。実際僕はテストする時無意識にダークモードにしています。目に優しいって大事。

今回は手っ取り早く確認できる方法の説明なので、パフォーマンス度外視です。なので画面で確認できたらテーマ管理やのまとめ方についてはご自身の環境に合わせてください。

スタイルの下準備

切替時のスタイルを設定し、それぞれのキーを設定します。

export const style = {
  light: {
    container: {
      backgroundColor: '#FFFFFF'
    }
  },
  dark: {
    container: {
      backgroundColor: '#000000'
    }
  }
}

超シンプルです。
コンポーネント側で切り替えを試してみます。

コンポーネントの下準備

設定画面でよく見るSwitchコンポーネントを使用して切り替えができるようにしてみます。

import React from 'react';
import { View, Switch } from 'react-native';
import { style } from './assets/style.ts';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { mode: 'light' }
  }

  render() {
    return (
      <View style={ style[this.state.mode] }>
        <Switch
          value={ this.state.mode === 'dark' }
          onValueChange={ value => this.changeMode(value) }
        />
      </View>
    );
  }

  changeMode(isDark: boolean) {
    this.setState({ mode: isDark ? 'dark' : 'light' })
  }
}

結構雑ですが、とりあえずこれでスイッチのON/OFFでライトモードとダークモードとを切り替えられると思います。

ただしこれだけだとスタイルの切り替え時の挙動しか確認できないので、デバイスの設定がどちらになっているかの判定はRNの味方npmに頼ります。(設定後のモードの保存は今回割愛してます。)
アプリ起動時にデバイス設定がダークモードであればアプリもダークモードになってると優しいですよね。

react-native-dark-modeを使う

npm i react-native-dark-modeでパッケージを追加します。

iOS

RNのバージョンが0.60以上の場合

cd ios && pod install # for iOS 

RNのバージョンが0.59以下の場合

react-native link react-native-dark-mode

Android

diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -13,7 +13,7 @@ 
                <activity
                        android:name=".MainActivity"
                        android:label="@string/app_name"
-                       android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
+                       android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
                        android:windowSoftInputMode="adjustResize">
                        <intent-filter>
                                <action android:name="android.intent.action.MAIN" />

各プラットフォームの依存関係を解決します。

初期値の設定

いろんなイベントが使えるのですが、その中からinitialModeを使います。
initialModeはデバイスの設定からlight or darkで返してくれるのでこれを初期化時にそのままステートで扱います。

import React from 'react';
import { View, Switch } from 'react-native';
import { initialMode } from 'react-native-dark-mode';
import { style } from './assets/style.ts';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { mode: initialMode }
  }

  render() {
    return (
      <View style={ style[this.state.mode] }>
        <Switch
          value={ this.state.mode === 'dark' }
          onValueChange={ value => this.changeMode(value) }
        />
      </View>
    );
  }

  changeMode(isDark: boolean) {
    this.setState({ mode: isDark ? 'dark' : 'light' })
  }
}

今回はものすごく単調なやり方になってますが、複雑な構造の場合、react-native-extended-stylesheetを使うと、綺麗にまとめることが出来ると思います。

参考になれば幸いです。