React Native製の公開しているアプリにダークモードを実装してみた


以前Qiitaで紹介したアプリペペロミアにダークモードを実装してみたので、実装方法について紹介します。

■ React Native製、予定作成アプリ「ペペロミア」を公開しました
https://qiita.com/wheatandcat/items/3324dfd141729e46009f

対象のアプリ: ペペロミア

■ アプリ公式サイト
https://peperomia.app/

GitHub

■ Peperomia
https://github.com/wheatandcat/Peperomia

環境

  • Expo SDK 34

実装してみた

動画にまとめました。

対象のpull request

■ ダークモードを実装する
https://github.com/wheatandcat/Peperomia/pull/108

実装方法

デザイン面

カラーを黒ベースに変えていきました。通常時とダークモード時を並べると、こんな感じです。

カラー定義

以下のカラー設定を決めて各要素に設定

■ PeperomiaNative/src/config/theme.ts
https://github.com/wheatandcat/Peperomia/blob/e9a4beac729938b6f88366324dbb8873144736b7/PeperomiaNative/src/config/theme.ts#L149-L163

要素名 通常 Darkモード
バックグラウンド white black
バックグラウンド(サブ) lightGray darkGray
テキスト black lightGray
テキスト(サブ) darkGray gray
アイコン black lightGray
チップ lightGray darkGray
チップテキスト black gray
ヘッダーバックグラウンド Green black
タブバーバックグラウンド lightGray darkGray

基本的には上記の要素のみカラーを変更して、それ以外は通常時とダークモードでカラーに差分は無いようしました。

画像の変更

例えば下のような画面を単純にカラー定義に当てはめると。。。

こんな感じになり画像と背景色が、かぶって画像が見えづらくなりました。

なので色を反転した画像を用意して、こんな感じになりました。(+ステータスバーのカラーも変えました)

これで良さ気です。

コード面

react-native-extended-stylesheetを使う

一般的にReactNativeのstyleの宣言はStyleSheetを使用すると思います。

StyleSheetは最初にstyleを宣言してしまうので、あとから変数でカラーの値を変更しても値は動的に変更されません。
なので、StyleSheetのラッパーであるreact-native-extended-stylesheetを使用してカラー設定をしていきます。

■ react-native-extended-stylesheet
https://github.com/vitalets/react-native-extended-stylesheet

こちらのライブラリは最初にカラー定義を設定して

import EStyleSheet from "react-native-extended-stylesheet";

const setLight = () => {
  EStyleSheet.build({
    $theme: "light",
    $background: "#fff",
    $text: "#333631"
  });
}

const setDark = () => {
  EStyleSheet.build({
    $theme: "dark",
    $background: "#333631",
    $text: "#fff"
  });
}

以下のようにstyleを指定します。

import React from "react";
import { View, Text } from "react-native";
import EStyleSheet from "react-native-extended-stylesheet";

export default () => (
  <View style={styles.root}>
    <Text style={styles.text}>テスト</Text>
  </View>
)

const styles = EStyleSheet.create({
  root: {
    height: "100%",
    backgroundColor: "$background", // ← EStyleSheet.buildで定義した変数
  },
  text: {
    color: "$text", // ← EStyleSheet.buildで定義した変数
  }
});

あとは、以下のメソッドを実行すれば通常(Light)モード

setLight()

以下のメソッドを実行すればダークモードとstyle内のカラー変更が可能になります。

setDark()

・・・と言いたいところですが、これだけでは完全には変わりません。

以下のドキュメントに記載している通り、一度レンダリングされてしまったコンポーネントに関してはEStyleSheet.buildを実行してもstyleの変更はされません。値が適応されるのは再レンダリングのタイミングです。

■ react-native-extended-stylesheetの反映タイミング
https://github.com/vitalets/react-native-extended-stylesheet#theming

なので、ドキュメントの通り一旦render部分をnullにして、再レンダリングをさせる必要があります。
ペペロミアでは、Providerでカラー設定の制御を持ち、一旦アプリケーションのレンダリングをnullにしてから再レンダリングする実装でダークモードを実現しています。

■ PeperomiaNative/src/containers/Theme.tsx (カラー設定を制御するProvider)
https://github.com/wheatandcat/Peperomia/blob/d1c9308e9ec2a27ca7cbd3afc865652106fcf784/PeperomiaNative/src/containers/Theme.tsx

ここまで実装すればダークモード完成です。

その他の使えそうな設定

今回は使用していませんが、各ライブラリにThemeの設定が存在するので、この辺を利用するともっと良い感じに実装できるかもしれません。

■ React Navigation Theme設定
https://reactnavigation.org/docs/en/themes.html

■ React Native Elements ThemeProvider設定
https://react-native-training.github.io/react-native-elements/docs/customization.html#using-themeprovider

まとめ

最初は1日くらいで出来るだろうと思いましたが、stylesheetで結構ハマって結局3日くらいかかってしまいました。
一応ネタのため実装してみましたが、正直まだReact Nativeでダークモードを実装するのは時期尚早な気がします。

iOS13とAndroid10のリリースで両方ともダークモードが実装され需用が高まるはずなので、もっと良い実装方法が増えそうな気がするので、今は待ったほうが良いかなーと実装して思いました。
(再レンダリングを明示的に書かなくてはいけないのは、余り良い実装な気もしないので。。。)

Expoでダークモードを使ってみる

まだダークモード搭載のアプリはリリースはしていませんが、Expoにはデプロイしているので以下から実機で起動すること可能です。