【Flutter】Flutterのproviderを使ってダークモード切り替え機能を実装する

Flutter パッケージのproviderの使い方を
下記のような`ダークモード切り替え機能のtooltip`を作成しながら学びます。

providerの使い方のイメージがイマイチで
もっと応用してるコードを参考しながら学びたい、
ダークテーマ機能初めて作ると思う方におすすめです。

![2025-01-16-02-15-56.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1212967/d9d97215-5704-93ce-2d8b-bf3a16a863c5.gif)

## providerとは?
Google公式が提供するアプリケーションでの`状態管理を簡単かつ効率的に行うためのパッケージ。`

https://pub.dev/packages/provider

## providerをインストール
```
flutter pub add provider
```
```pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  provider: ^6.1.2 #2025/1/15時点の最新バージョン
```

## providerの動きの図

```
+------------------------+ create    +-----------------+    child   +-----------------+
| ChangeNotifierProvider |---------->|   ChangeModel   |----------->|      MyApp      |
+------------------------+           +-----------------+            +-----------------+
        ^                                     |                         |
        |                                     |                         v
        | notifyListeners()                   |                         +-----------------+
        |                                     |                         |     Consumer    |
        |                                     |                         +-----------------+
        |                                     |                                 |
        |                                     |                                 v
        |                                     |                         +-----------------+
        |                                     |                         | MyHomePage      |
        |                                     |                         +-----------------+
        |                                     |                                 |
        |                                     |                      Provider.of<ChangeModel>(context)
        |                                     +-----------------------------------------+
        |
        +-------------------------------------------------------------------------------+
                             onPressed: changeModelProvider.changeDarkmode()
```


## コード全体
```dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

// モデルを作成
class ChangeModel with ChangeNotifier {
  bool _isDarkmodeFlag = false; // プライベート変数
  bool get isDarkmodeFlag => _isDarkmodeFlag; // ゲッター

  void changeDarkmode() {
    _isDarkmodeFlag = !_isDarkmodeFlag; // ダークモードフラグを切り替え
    notifyListeners(); // 変更をリスナーに通知してUIを更新
    print(_isDarkmodeFlag);
  }
}

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => ChangeModel(), // 提供するモデルを指定
      child: MyApp(), // アプリ本体
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer<ChangeModel>(// Consumerを使って、ChangeModelの更新を監視
        builder: (context, changeModelProvider, child) {
      return MaterialApp(
        title: 'Flutter Demo provider test',
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(
            seedColor: Colors.lime,
            brightness: changeModelProvider.isDarkmodeFlag //フラグのbool値でテーマを指定する
                ? Brightness.dark
                : Brightness.light,
          ),
          useMaterial3: true,
        ),
        home: const MyHomePage(),
      );
    });
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    //Provider.of<T>(context) は、context を起点にウィジェットツリーを遡り、T 型のオブジェクトを提供している Provider を探します。
    final changeModelProvider = Provider.of<ChangeModel>(context); // インスタンスを取得
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Tooltip(
              // toolchipを作成
              message: 'Change brightness mode',
              child: IconButton(
                icon: Icon(
                  changeModelProvider.isDarkmodeFlag //フラグを使って表示するアイコンを変える
                      ? Icons.brightness_2_outlined
                      : Icons.wb_sunny_outlined,
                ),
                onPressed: () {
                  changeModelProvider.changeDarkmode(); // ダークモードを切り替え
                },
              ),
            ),
            Text("Hello World"),
          ],
        ),
      ),
    );
  }
}

```


## 主要コードを噛み砕く
#### パッケージをインポート
```dart
import 'package:provider/provider.dart'; 
```
#### モデルの作成 (ダークモードに切り替える内部の処理を作成)
・Flutterのfoundationパッケージに含まれるクラス`ChangeNotfier`を継承して`モデルを作成`。

・モデルの中にフラグ変更処理の`changeDarkmode()`メソッドを作成

```dart
class ChangeModel with ChangeNotifier {
  bool _isDarkFlag = false; //プライベート変数
  bool get isDarkMode => _isDarkFlag; //ゲッタ-
  void changeDarkmode() {
    _isDarkFlag = !_isDarkFlag;
  }
```

#### ConsumerでUIを更新
・`Consumerウィジェット`を使って、`ChangeModel`の変更を監視し、UIを更新できるようにする
・`builder引数で、ChangeModelの値に基づいてUIを構築`する関数を指定してます。
```dart
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer<ChangeModel>( // Consumerを使って、ChangeModelの更新を監視
        builder: (context, changeModelProvider, child) {
      return MaterialApp(
        // ...
      );
    });
  }
}
```

#### テーマの切り替え
・colorSchemeウィジェットでflagの値でテーマを変えるよう指定
```dart
      return MaterialApp(
        title: 'Flutter Demo provider test',
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(
            seedColor: Colors.lime,
            brightness: changeModelProvider.isDarkmodeFlag //フラグのbool値でテーマを指定する
                ? Brightness.dark
                : Brightness.light,
          ),
          useMaterial3: true,
        ),
        home: const MyHomePage(),
      );
    });
  }
```

#### tooltipを作成
・`ChangeModelのインスタンスを取得`して、ダークモードのフラグ変更処理を実行できるようtoolchipeの押した後の処理(onPressed)に書く
```dart
class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    //Provider.of<T>(context) は、context を起点にウィジェットツリーを遡り、T 型のオブジェクトを提供している Provider を探します。
    final changeModelProvider =
        Provider.of<ChangeModel>(context); // インスタンスを取得
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Tooltip(
              // toolchipを作成
              message: 'Change brightness mode',
              child: IconButton(
                icon: Icon(
                  changeModelProvider.isDarkmodeFlag //フラグを使って表示するアイコンを変える
                      ? Icons.brightness_2_outlined
                      : Icons.wb_sunny_outlined,
                ),
                onPressed: () {
                  changeModelProvider.changeDarkmode(); // ダークモードを切り替え
                },
              ),
            ),
            Text("Hello World"),
          ],
        ),
      ),
    );
  }
}
```

## まとめ
今回は、providerパッケージを使ってダークモード切り替え機能を実装する方法を紹介しました。
providerは状態管理を簡単に行うことができるので、ぜひ活用してみてください。


## 参考資料
[provider | Flutter Package](https://pub.dev/packages/provider)
[Flutter 公式ドキュメント - Provider](https://www.google.com/url?sa=E&source=gmail&q=https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple)