FlutterのRiverpodとHooksの基本のキを試してみた

---
# :book: Flutterの記事を整理し本にしました :book:
https://zenn.dev/kazutxt/books/flutter_practice_introduction

* <font color="red"><b>本稿の記事を含む様々な記事を体系的に整理し本にまとめました</b></font>
* 今後はこちらを最新化するため、<font color="red"><b>最新情報はこちらをご確認ください</b></font>
* 30万文字を超える超大作になっています!!

---

## まとめ記事
* [Flutter系の記事のまとめ](https://qiita.com/kazutxt/items/524acb1c134015682ccf)

# はじめに
* Providerの完了であるRiverpodがいいという記事がたくさんあるものの、あまり気にしていなかったので調べてみました。


# Riverpodとは

**Riverpodとは、Providerパッケージと同じ製作者が作った、状態管理を行うためのパッケージです。**

**Riverpod**と**Provider**はアナグラムにもなっています。

## 改善点
**Provider**と比べて、以下のような改良が行われています。

* グローバルでの定義が可能
* ランタイムエラーを起こさない
* 同じ型での複数探索が可能
* BuildContext配下にする必要がない/追加Widgetが少なくてすむ
* Hooks的な使い方もできる

# Provider
## Providerで作ったSliderの確認

まず、Providerパターンのチャプターで作ったSliderで値を変えるプログラムを確認します。

![pic1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/329115b3-1e0d-88f5-0608-7512dd3213bd.png)


まずは、データとなります。
`ChangeNotifier`を継承し、値が変わったタイミングで`notifyListeners`で通知をします。

```dart:mydata.dart
import 'package:flutter/foundation.dart';

class MyData with ChangeNotifier {
  double _value = 0.5;
  // getter
  double get value => _value;
  // setter
  set value(double value) {
    _value = value;
    notifyListeners();
  }
}
```

続いてスライダーです。
`Provider.of<MyData>(context)`で`MyData`を習得し、`MyData`の`value`の取得や変更を行います。

```dart:slider.dart
import 'package:flutter/material.dart'
import 'package:hello_world/mydata.dart';
import 'package:provider/provider.dart';

class MySlider extends StatefulWidget {
  @override
  createState() => _MySliderState();
}

class _MySliderState extends State<MySlider> {
  @override
  Widget build(BuildContext context) {
    final mydata = Provider.of<MyData>(context);
    return Slider(
        value: mydata.value, onChanged: (value) => mydata.value = value);
  }
}
```

最後に呼び出す`main`になります。
画面の`Text`の表示と`Slider`による入出力を行います。

```dart:main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'mydata.dart';
import 'slider.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, this.title}) : super(key: key);
  final String? title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (BuildContext context) => MyData(),
      child: Scaffold(
          appBar: AppBar(
            title: Text(widget.title!),
          ),
          body: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Consumer<MyData>(
                builder: (context, mydata, _) => Text(
                  mydata.value.toStringAsFixed(2),
                  style: TextStyle(fontSize: 100),
                ),
              ),
              MySlider(),
            ],
          )),
    );
  }
}

```

# Riverpod
続いて、Riverpodでカスタマイズしていきます。

## 画面イメージ
動作は変わりません

![pic2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/346e536b-3721-9f21-334c-f507e5949f78.png)

## パッケージ
まず、必要なパッケージをインストールします。

```yaml:pubspec.yaml
  flutter_riverpod: ^0.14.0+3
```

## 実装

まずは、データ部分です。

データの方は、継承するものが`ChangeNotifier`ではなく、`StateNotifier`に変わっています。
また、`state`という暗黙的なフィールドがあり、型はジェネリクスで指定します。
`state`を利用するためのgetter/setterは不要で、通知するための`notifyListeners`も必要ありません。

```dart:mydata.dart
import 'package:riverpod/riverpod.dart';

class MyData extends StateNotifier<double> {
  MyData() : super(0.5);
  void changState(newState) => this.state = newState;
}
```

つづいて、`main`になります。
`Slider`はコンパクトになったので、ファイルは分けずにmainにまとめています。

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

// 1.グローバル変数にProviderを設定
final _mydataProvider =
    StateNotifierProvider<MyData, double>((ref) => MyData());

void main() {
  // 2.ProviderScopeを設定
  runApp(
    ProviderScope(
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, this.title}) : super(key: key);
  final String? title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title!),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          // 3.ConsumerWidgetを使い、watchを使えるようにする
          Consumer(builder: (context, watch, child) {
            return Text(
              // 4.watch関数にプロバイダーを渡し、stateを取り出す
              '${watch(_mydataProvider).toStringAsFixed(2)}',
              style: TextStyle(fontSize: 100),
            );
          }),
          Consumer(builder: (context, watch, child) {
            return Slider(
                value: watch(_mydataProvider),
                // 5.context.readにプロバイダーのnotifierを与えて、メソッドを呼び出す
                onChanged: (value) =>
                    context.read(_mydataProvider.notifier).changState(value));
          }),
        ],
      ),
    );
  }
}

```

ポイントになる部分を下記に整理します。

1. グローバル変数にProviderを設定
  * `StateNotifierProvider<MyData, double>((ref) => MyData())`
  * グローバル変数でProviderを準備します
2. ProviderScopeを設定
  * `runApp(ProviderScope(child: MyApp()))`
  * アプリ内でProviderが使えるようにします
3. ConsumerWidgetを使い、watchを使えるようにする
  * `Consumer(builder: (context, watch, child) { /*中略*/})`
  * watchでプロバイダーにアクセスできるようにします。
4. watch関数にプロバイダーを渡し、stateを取り出す
  * `watch(_mydataProvider)`
  * watchでプロバイダーを指定すると、stateの値を取得できます。
5. context.readにプロバイダーのnotifierを与えてメソッドを呼び出す
  * `context.read(_mydataProvider.notifier).changState())`
  * 値だけではなく、メソッドにもアクセスできます。

# Hooks
ReactのHooksスタイルでもアクセスできます。

## パッケージ
まず、必要なパッケージをインストールします。

```pubspec.yaml
  flutter_riverpod: ^0.14.0+3
+ hooks_riverpod: ^0.14.0+4
```

つづいて、変更点を確認していきます。
`HookWidget`を継承し、`userProvider`でアクセスすれば、`Consumer`を使わずにすませることができます

## 実装

```dart:main.dart
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:riverpod/riverpod.dart';
import 'mydata.dart';

final _mydataProvider =
    StateNotifierProvider<MyData, double>((ref) => MyData());

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, this.title}) : super(key: key);
  final String? title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title!),
        ),
        body: MyContents());
  }
}

class MyContents extends HookWidget {
  @override
  Widget build(BuildContext context) {
+    final _value = useProvider(_mydataProvider);

    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
-      children: [
-        Consumer(builder: (context, watch, child) {
          return
        Text(
+          '${_value.toStringAsFixed(2)}',
          style: TextStyle(fontSize: 100),
-         *);
-        }
        ),
-        Consumer(builder: (context, watch, child) {
-          return
        Slider(
+            value: _value,
            onChanged: (value) =>
                context.read(_mydataProvider.notifier).changState(value)
-         *);
-        }
        )
      ],
    );
  }
}
```