どうしてもriverpodを使って自分自身を削除するボタンを作りたかったメモ【Flutter】

間違っている箇所、謎な箇所があったら指摘してください。

## 動機 
riverpodの勉強がしたかった（建前）
自滅するWidgetが欲しかった（本音） 

## 前提知識 
riverpodをほんの少しだけ知っている。

## バージョン 
| | バージョン |
|:-:|:-:|
| Flutter | 3.3.0  |
| Dart | 2.18.0 |
| flutter_riverpod  | ^2.2.0  |
| Xcode | 14.2 |
 
## 概要 
`ElevatedButton`は`StateNotifier`のサブクラス越しに挿入する。 
`StateNotifierProvider`を使って、ボタンをタップした時にWidgetをnullにする。　
今回はiOSシミュレーターを使ってる。 

重要な部分だけ軽く抑えて、最後に全体のコードを貼る。

## 実装 
### provider辺りの部分
```dart 
final buttonTogglerProvider =
    StateNotifierProvider<ButtonToggler, VanishButton?>(
        (ref) => ButtonToggler());

class ButtonToggler extends StateNotifier<VanishButton?> {
  ButtonToggler() : super(const VanishButton());

  void toggle() => state = state == null ? const VanishButton() : null;
}
``` 
Widgetとnullで切り替えたかったので、型は`VanishButton?`を指定している。　

`ButtonToggler`内に`toggle()`を定義することで、自発的にWidgetを削除し、任意のタイミングで再構築できる。（←まさにこれがしたかった！！）
`StateNotifierProvider`にしたのは、Widgetとnullの切り替えを自分で作りたかったから。他のでもいいかもしれないけど、公式パッと見た感じこれがベストっぽかった。　
 
[プロバイダとは](https://riverpod.dev/ja/docs/concepts/providers)でproviderの種類が確認できる。

`state`のとこに`ButtonTogger`の状態が格納されてるイメージ。基本的にこのstateを参照していくっぽい。stateはもともとあるものなので自分で宣言したりしなくていい、てかしない。　
　
### ElevatedButtonの挙動のとこ　
```dart 
class VanishButton extends ConsumerWidget {
  const VanishButton({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return ElevatedButton(
      onPressed: () {
        ref.read(buttonTogglerProvider.notifier).toggle();
      },
      style: ElevatedButton.styleFrom(
        backgroundColor: Colors.amber,
      ),
      child: Text(
        ref.watch(buttonTogglerProvider).toString(),
      ),
    );
  }
}
```
`ref`で監視とかできるように`ConsumerWidget`を継承する。 
 
前項の`ButtonToggler`で`VanishButton`を渡してるのは、このボタンを消したいから。そのため、このボタンの`onPress`内でtoggle()を呼んで、間接的に自分をnullにして消す。（逆に渡したいのでクラスを定義するみたいなとこある）

`onPress`の様な、ユーザーのアクションに起因する処理の中には`watch()`ではなく`read()`を使うらしい。read()には`buttonTogglerProvider.notifier`を渡すところがwatch()と異なる。 
watchは監視をするだけ？っぽい。

>watch メソッドは ElevatedButton の onPressed 内など、非同期的な場面で呼び出さないでください。 また initState を始め、State のライフサイクルメソッド内での使用も避けてください。
>これらの場合は代わりに ref.read を使用してください。

>ref.read はリアクティブではないため、可能な限り使用を避けてください。
>watch や listen の使用では問題が生じる場合の回避策として存在しています。 ほとんどの場面では watch や listen の使用、特に watch の使用がベターなはずです。
>【重要】 ref.read は build メソッドの中で使わない

らしい。[^1] 


`ref.watch()`は今回対して関係ないが、これで状態を監視できる。 

### スコープ 
```dart 
void main() {
  runApp(const ProviderScope(child: MyApp()));
}
```
`main()`でちゃんとスコープを決めとるのを忘れないのが大事。

## 実行結果 (GIF)
![Simulator_Screen_Recording_-_iPhone_14_Pro_Max_-_2023-02-14_at_11_11_28_AdobeExpress.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2180964/3ffd4c45-7850-043f-5ea9-e96248ad13ad.gif)

vanishButton消えちゃうので復活用のボタンも用意してた。
 
状態の変化がわかりやすいように一応テキストも変えた。

何もない時に復活ボタンを押しても何も起きない。


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

final buttonTogglerProvider =
    StateNotifierProvider<ButtonToggler, VanishButton?>(
        (ref) => ButtonToggler());

class ButtonToggler extends StateNotifier<VanishButton?> {
  ButtonToggler() : super(const VanishButton());

  void toggle() => state = state == null ? const VanishButton() : null;
}

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const CountPage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("title"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const SizedBox(height: 50),
            Container(
              height: 50,
              child: ref.watch(buttonTogglerProvider),
            ),
            const SizedBox(height: 50),
            Container(
              height: 50,
              child: ElevatedButton(
                onPressed: () {
                  ref.watch(buttonTogglerProvider) ??
                      ref.read(buttonTogglerProvider.notifier).toggle();
                },
                child: Text(
                  ref.watch(buttonTogglerProvider) == null
                      ? "restore button"
                      : "do noting",
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return ElevatedButton(
      onPressed: () {
        ref.read(buttonTogglerProvider.notifier).toggle();
      },
      style: ElevatedButton.styleFrom(
        backgroundColor: Colors.amber,
      ),
      child: Text(
        ref.watch(buttonTogglerProvider).toString(),
      ),
    );
  }
}
```

## 参考
* [https://riverpod.dev/ja/docs/concepts/reading](https://riverpod.dev/ja/docs/concepts/reading)
* [https://riverpod.dev/ja/docs/concepts/providers](https://riverpod.dev/ja/docs/concepts/providers)
* https://www.flutter-study.dev/firebase-app/riverpod

[^1]: [https://riverpod.dev/ja/docs/concepts/reading](https://riverpod.dev/ja/docs/concepts/reading)