LoginSignup
6
4

[Flutter]ライトモードとダークモードの切替はRiverpodで実装しよう

Last updated at Posted at 2022-07-23

はじめに

Flutterのフレームワーク Riverpod では、状態の管理を適切に簡潔に行うことができます。
この記事ではライトモードとダークモード(以下、外観モードと呼びます)の切替を Riverpod 経由で設定する方法を説明し、外観モードの簡単な設定方法と Riverpod の簡単な使い方について説明したいと思います。

この記事の出来上がるもの

ダウンロード.gif

Riverpod の使用するための設定

Riverpod には3種類ありますが、今回は一番シンプルな flutter_riverpod を使用します。

スクリーンショット 2022-07-23 14.11.43.png
Riverpod公式ドキュメントより引用

以下では flutter_riverpod の利用手引きをご紹介します。

flutter_riverpod パッケージをインストールする

pubspec.yaml に以下の依存関係を記載して、ターミナルでflutter pub getを実行してください。

pubspec.yaml
environment:
  sdk: ">=2.12.0 <3.0.0"
  flutter: ">=2.0.0"

dependencies:
  flutter:
    sdk: flutter
  flutter_riverpod: ^2.0.0-dev.9
  # flutter_riverpod のバージョンは、公式サイトから逐一確認してください。

flutter_riverpod を import する

以下を main.dart に追加してください

main.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

エントリーポイントに ProviderScope を追加する

地味に忘れがちですが、Flutterアプリのエントリーポイント void main() において ProviderScope() の宣言が必要になります。
Riverpod を扱う際は忘れないようにしましょう......!

main.dart
void main() {
  runApp(
    // MyApp を ProviderScope で括ってあげる
    const ProviderScope(
      child: MyApp()
    )
  );
}

ThemeModeにStateProviderを設定する

ThemeMode プロパティは、 MaterialApp の theme と darkTheme の両方が提供されている場合にアプリで使用される外観モードを決定します。以下はThemeModeプロパティの持つ値です。

ThemeMode 外観設定
.system システム設定に従う
.light ライトモード
.dark ダークモード

ThemeModeに対して Riverpod の StateProvider を作ってあげると、外観モードの状態管理が簡単にできます。

main.dart
// デフォルトはシステム設定に従う(ThemeMode.system)ようにしておくといいかも
final themeModeProvider = StateProvider<ThemeMode>((ref)=>ThemeMode.system);

refオブジェクトを取得してthemeModeの状態を管理する

ウィジェットの中で上述の themeModeProvider を扱うために、refオブジェクトを取得する必要があります。

そのために通常の StatelessWidget ではなく、ConsumerWidget を用います。ConsumerWidget は StatelessWidget とほとんど変わりませんが、 build の引数に WidgetRef を取るようになります。
WidgetRef(ref)オブジェクトに対してref.watch メソッドを使用すると任意のプロバイダーを監視することができます。
themeModeProviderを用いてfinal themeMode = ref.watch(themeModeProvider.state);と宣言すると任意のプロバイダーを監視するオブジェクトが扱えます。
実際の使用例は以下の通りです。

main.dart
// 1. ConsumerWidget を継承したクラスを作成する
class MyApp extends ConsumerWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  // 2. 引数に WidgetRef ref を取るようにする
  Widget build(BuildContext context, WidgetRef ref) {
    // 3. themModeProvider の ref オブジェクトを取得する
    final themeMode = ref.watch(themeModeProvider.state);
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Brightness Demo',
      theme: ThemeData.light(),
      darkTheme: ThemeData.dark(),
      // 4. themeModeProvider の state を用いて themeMode を設定する
      themeMode: themeMode.state,
      home: const MyHomePage(),
    );
  }
}

外観モードを選択するUIを作る

上述の themeModeProvider を経由して、アプリの外観モードを選択するUIを作ります。
今回は PopUpMenuButton を用います。 PopupMenuButton の使い方は詳しくは説明しませんが、以下コードや PopupMenuButton のドキュメントを参考にしてください。

まずbuildメソッド内でrefオブジェクトを取得します。
final themeMode = ref.watch(themeModeProvider.state);

次にPopupMenuButtonのonSelectedでthemeMode.stateにThemeModeを入れてあげます。
onSelected: (ThemeMode selectedThemeMode) => themeMode.state = selectedThemeMode,

これだけで単純に代入を行うだけでアプリの外観モードが設定されるようになります。

main.dart
// 1. ConsumerWidgetを継承する
class MyHomePage extends ConsumerWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  // 2. 引数に WidgetRef ref を取る 
  Widget build(BuildContext context, WidgetRef ref) {
    // ref.watch() を宣言する
    final themeMode = ref.watch(themeModeProvider.state);
    return Scaffold(
      appBar: AppBar(
        title: const Text("Brightness Demo"),
        actions: [
          Padding(
            padding: const EdgeInsets.all(10.0),
            child: PopupMenuButton<ThemeMode>(
              icon: const Icon(Icons.settings_brightness,),
              // themeMode.state に選択された 外観モード をセットする
              onSelected: (ThemeMode selectedThemeMode) => themeMode.state = selectedThemeMode,
              itemBuilder: (context) => <PopupMenuEntry<ThemeMode>>[
                const PopupMenuItem(
                  value: ThemeMode.system,
                  child: Text('システム設定に従う'),
                ),
                const PopupMenuItem(
                  value: ThemeMode.light, 
                  child: Text('ライトモード'),
                ),
                const PopupMenuItem(
                  value: ThemeMode.dark,
                  child: Text('ダークモード'),
                ),
              ],
            ),
          ),
        ],
      ),
      body: const Center(
        child: Text(
          "設定に応じてfontColorが入れ替わる", 
          style: TextStyle(fontSize: 30.0),
        ),
      ),
    );
  }
}

コード全文

以上をまとめると以下のようになります。
これをコピペして動作確認してみてください。

main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

// アプリ全体の外観モードの状態を管理するプロバイダー
final themeModeProvider = StateProvider<ThemeMode>((ref)=>ThemeMode.system);

class MyApp extends ConsumerWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final themeMode = ref.watch(themeModeProvider.state);
    return MaterialApp(
      // AppBarのactionsと被っちゃうので画面右上のDEBUG帯を無効化する
      debugShowCheckedModeBanner: false,
      title: 'Brightness Demo',
      theme: ThemeData.light(),
      darkTheme: ThemeData.dark(),
      themeMode: themeMode.state,
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends ConsumerWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final themeMode = ref.watch(themeModeProvider.state);
    return Scaffold(
      appBar: AppBar(
        title: const Text("Brightness Demo"),
        actions: [
          Padding(
            padding: const EdgeInsets.all(10.0),
            child: PopupMenuButton<ThemeMode>(
              icon: const Icon(Icons.settings_brightness,),
              onSelected: (ThemeMode selectedThemeMode) => themeMode.state = selectedThemeMode,
              itemBuilder: (context) => <PopupMenuEntry<ThemeMode>>[
                const PopupMenuItem(
                  value: ThemeMode.system,
                  child: Text('システム設定に従う'),
                ),
                const PopupMenuItem(
                  value: ThemeMode.light, 
                  child: Text('ライトモード'),
                ),
                const PopupMenuItem(
                  value: ThemeMode.dark,
                  child: Text('ダークモード'),
                ),
              ],
            ),
          ),
        ],
      ),
      body: const Center(
        child: Text(
          "設定に応じてfontColorが入れ替わる", 
          style: TextStyle(fontSize: 30.0),
        ),
      ),
    );
  }
}

実行環境

% flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.0.5, on macOS 12.4 21F79 darwin-x64, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 32.1.0-rc1)
[✓] Xcode - develop for iOS and macOS (Xcode 13.3)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2021.1)
[✓] VS Code (version 1.69.1)
[✓] Connected device (2 available)
[✓] HTTP Host Availability
6
4
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
4