はじめに
Flutterのフレームワーク Riverpod では、状態の管理を適切に簡潔に行うことができます。
この記事ではライトモードとダークモード(以下、外観モードと呼びます)の切替を Riverpod 経由で設定する方法を説明し、外観モードの簡単な設定方法と Riverpod の簡単な使い方について説明したいと思います。
この記事の出来上がるもの
Riverpod の使用するための設定
Riverpod には3種類ありますが、今回は一番シンプルな flutter_riverpod を使用します。
Riverpod公式ドキュメントより引用
以下では flutter_riverpod の利用手引きをご紹介します。
flutter_riverpod パッケージをインストールする
pubspec.yaml に以下の依存関係を記載して、ターミナルでflutter pub get
を実行してください。
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 に追加してください
import 'package:flutter_riverpod/flutter_riverpod.dart';
エントリーポイントに ProviderScope を追加する
地味に忘れがちですが、Flutterアプリのエントリーポイント void main()
において ProviderScope()
の宣言が必要になります。
Riverpod を扱う際は忘れないようにしましょう......!
void main() {
runApp(
// MyApp を ProviderScope で括ってあげる
const ProviderScope(
child: MyApp()
)
);
}
ThemeModeにStateProviderを設定する
ThemeMode プロパティは、 MaterialApp の theme と darkTheme の両方が提供されている場合にアプリで使用される外観モードを決定します。以下はThemeModeプロパティの持つ値です。
ThemeMode | 外観設定 |
---|---|
.system | システム設定に従う |
.light | ライトモード |
.dark | ダークモード |
ThemeModeに対して Riverpod の StateProvider を作ってあげると、外観モードの状態管理が簡単にできます。
// デフォルトはシステム設定に従う(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);
と宣言すると任意のプロバイダーを監視するオブジェクトが扱えます。
実際の使用例は以下の通りです。
// 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,
これだけで単純に代入を行うだけでアプリの外観モードが設定されるようになります。
// 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),
),
),
);
}
}
コード全文
以上をまとめると以下のようになります。
これをコピペして動作確認してみてください。
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),
),
),
);
}
}
実行環境
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