【Flutter】Riverpodで状態管理をやってみた

# ○はじめに
おっさんエンジニアj_haraです。
今回の記事ではFlutterの状態管理について触れたいと思います。
背景としては最近業務にてFlutterを使った案件が多く、少し勉強しておこうかと。
記事の内容は、Flutterで新規プロジェクトを作成した際にデフォルトで作成されるカウントアップのアプリを少し変更し、使用しRiverpodの状態管理で実装してみます。

# ○Riverpodとは・・・
Riverpodとは冒頭にも少し触れていますが「状態管理ライブラリ」です。
Providerのライブラリ開発者がアップデート版としてリリースしたそう。
Riverpodの特徴については下記にまとまっていましたのでご参考に。
https://zenn.dev/riscait/books/flutter-riverpod-practical-introduction/viewer/what-is-riverpod

# ○実践

では早速やっていきましょう

### 1. Rverpodのライブラリを使用できるようにする
今回は、riverpod2.3.6を使用しています。
下記URLよりページを開き、タイトルをコピーします。
https://pub.dev/packages/flutter_riverpod
![pub.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2262606/5927abd6-83c3-c3d3-b3b8-7aa1bab423e3.png)

pubspec.yamlへペースト。(下記の一番下に貼り付け)
```pubspec.yaml
  dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  flutter_riverpod: ^2.3.6
```
これだけでこのプロジェクトでriverpodが使用できるようになります。
すごく簡単。
### 2. 実装
#### 2-1. 状態の実装

まずは状態の実装から。
今回では、カウントアップの元情報となるカウントがそれにあたります。

```my_home_page_state.dart
class MyHomePageState {
  //コンストラクタ
  const MyHomePageState({this.counter = 0});
  final int counter;

  MyHomePageState copyWith(int counter) => 
  MyHomePageState(counter: counter);
}
```
#### 2-2. ロジックの実装
ロジック部分にはカウントアップ/ダウンボタンがクリックされた時の処理を記載します。
stateにはMyHomePageStateのインスタンスがはいります。
```my_home_page_view_model.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../state/my_home_page_state.dart';

class MyHomePageStateNotifier extends StateNotifier<MyHomePageState>{
  MyHomePageStateNotifier(): super(const MyHomePageState());

  void increment() {
    state = state.copyWith(state.counter + 1);
  }

  void decrement() {
    state = state.copyWith(state.counter - 1);
  }
}
```

#### 2-3. 状態とロジックの紐づけ
上記にて作成した状態とロジックを管理するプロバイダーを定義します。
この後に実装する見た目部分のカウント表示、カウントアップ/ダウンボタンの部分で使用します。
```main.dart
final myHomePageProvider = StateNotifierProvider<MyHomePageStateNotifier, MyHomePageState>
  ((ref) => MyHomePageStateNotifier());
```

#### 2-4. 見た目部分の実装
見た目部分としては、カウント表示部、カウントアップ/ダウンボタンを実装しています。
myHomePageProvider経由で、それぞれ状態やロジックへアクセスすることで、異なるWidget間でデータを共有することが出来ています。
※今回は、Widget間のデータの共有をおこないたかったため、カウント表示部、カウントアップ/ダウンするボタンそれぞれをWidgetとしています。
```my_home_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../main.dart';

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

  @override
  Widget build(BuildContext context) {
    return ProviderScope(
      child: Scaffold(
        appBar: AppBar(
          title: const Text('RiverpodPage'),
        ),
        body:const Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              //カウント表示部
              WidgetCountDisp(),
              //カウントアップボタン
              WidgetCountup(),
              //カウントダウンボタン
              WidgetCountdown(),
            ],
          ),
        ),
      ),
    );
  }
}

//カウント表示部
class WidgetCountDisp extends ConsumerWidget {
  const WidgetCountDisp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final int counter = ref.watch(myHomePageProvider).counter;
    return Text(
      '$counter',
      style: Theme.of(context).textTheme.bodyMedium,
    );
  }
}

//カウントアップボタン
class WidgetCountup extends ConsumerWidget {
  const WidgetCountup({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final Function increment = ref.read(myHomePageProvider.notifier).increment;
    return ElevatedButton(
      onPressed: (){
        increment();
      },
      child: const Text('カウントアップ'),
    );
  }
}

//カウントダウンボタン
class WidgetCountdown extends ConsumerWidget {
  const WidgetCountdown({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final Function decrement = ref.read(myHomePageProvider.notifier).decrement;
    return ElevatedButton(
      onPressed: (){
        decrement();
      },
      child: const Text('カウントダウン'),
    );
  }
}

```

# ○フォルダ構成
上記ソースのフォルダ構成です。以下のようになっています。
```
├── lib
   ├── state 
   │    ├── my_home_page_state.dart
   ├── view 
   │    ├── my_home_page.dart
   ├── View_model 
   │    ├── my_home_page_view_model.dart
   └── main.dart
```
# ○まとめ
今回は、Riverpodを用いた状態管理についてやってみました。
RiverpodではProviderをグローバルに定義することにより、Widget間で状態を共有することができました。状態、ロジック、見た目を別々に実装できるので、ファイルも別ファイルとして管理しやすく保守もしやすそうですね。

以下、今回作成したアプリです。
<img width="200" alt="apri.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2262606/805845a7-32c3-6cdb-7512-f2a86a29217e.gif">