StreamとProvider備忘録

# 背景
Flutterでアプリ開発を行った際、非同期処理や状態管理にStreamとProviderを知ったが違いがイマイチわからない。
それぞれどんな特徴があるかまとめる。

# Stream

https://dart.dev/libraries/async/using-streams

直訳すると「流れ」
宣言したStream上に「値」を流すイメージ

1. `sink.add`で値をStreamに流し
2. `steram.listen()`で受け取る
他記事でも見られる例
```
import 'dart:async';

main() {
  final controller = StreamController();
  // 値を流す
  controller.sink.add(1);
  controller.sink.add(2);
  
  // 値を受け取る  
  controller.stream.listen((value){
    print(value);
  });
}
```

1秒ごとに数字をカウントしていくコード
```
import 'dart:async';

Stream<int> countStream(int to) async* {
  for (int i = 1; i <= to; i++) {
    yield i;
    await Future.delayed(const Duration(seconds: 1));
  }
}

void main() async {
  var stream = countStream(10);
  stream.listen((num) => print(num));
}

```

`yield`は返り値のStreamを流せる
`async*`：返り値はStream
`async`：返り値はFuture


### StreamController

https://api.flutter.dev/flutter/dart-async/StreamController-class.html

Streamに複数の状態を管理できる

```
final streamController = StreamController(
  onPause: () => print('Paused'),
  onResume: () => print('Resumed'),
  onCancel: () => print('Cancelled'),
  onListen: () => print('Listens'),
);

streamController.stream.listen(
  (event) => print('Event: $event'),
  onDone: () => print('Done'),
  onError: (error) => print(error),
);
```

`fiz()`と`buz()`がStreamを呼び出すコード
1秒ごとに'call!'が投げられる→受け取ったときに文字列表示
`StreamController< T >.broadcast`で複数回呼び出せるStreamが作成できる
```
import 'dart:async';

final _controller = StreamController<String>.broadcast();

main() async {
  fiz();
  buz();

  while (true) {
    _controller.sink.add('call!');
    await Future.delayed(Duration(seconds: 1));
  }
}

void fiz() {
  _controller.stream.listen((val) {
    print('fiz');
  });
}

void buz() {
  _controller.stream.listen((val) {
    print('buz');
  });
}
```

### StreamBuilder

https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html

Streamに応じて画面の再描画を行えるWidget
基本構造は以下のコード
`stream`:監視するStreamを指定
`initialData`:Streamからデータが届くまでの初期値
`builder`:Streamにデータが流れてきた時、`BuildContext`と`AsyncSnapshot<T>`で描画処理を行う
`AsyncSnapshot<T>`:Streamから受け取ったデータやエラー情報を含むオブジェクト `T`はデータ型

```
StreamBuilder<T>(
  stream: stream,
  initialData: initialData,
  builder: (BuildContext context, AsyncSnapshot<T> snapshot) {
    // ウィジェットの再描画処理
    if(snapshot.hasData){
        return Test("Data:[$snapshot.data]");
    }
  },
)
```

# Provider

https://pub.dev/packages/provider

親Widgetから子Widgetにデータを渡すことができるライブラリ
`StatefulWidget`を使わず`StatelessWidget`で書くことができるため、無駄な再描画を避けることができる

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

void main() {
  runApp(
    MultiProvider(
      providers: [ChangeNotifierProvider(create: (_) => Counts())],
      child: MyApp(),
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(title: 'Provider', home: Home());
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Provider Test')),
      body: ChangeNotifierProvider(
        create: (BuildContext context) => Counts(),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [Counter(), Buttons()],
          ),
        ),
      ),
    );
  }
}

class Counts with ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void add() {
    _count++;
    notifyListeners();
  }

  void remove() {
    _count--;
    notifyListeners();
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        ElevatedButton(
          onPressed: () {
            context.read<Counts>().add();
          },
          child: const Text("count up"),
        ),
        const SizedBox(width: 40),
        ElevatedButton(
          onPressed: () {
            context.read<Counts>().remove();
          },
          child: const Text("count down"),
        ),
      ],
    );
  }
}

class Counter extends StatelessWidget {
  const Counter({super.key});
  @override
  Widget build(BuildContext context) {
    return Text(context.watch<Counts>().count.toString());
  }
}

```

`ChangeNotifier`
ChangeNotifier はリスナーに変更通知を行う機能を提供しているクラス
CountsクラスはChangeNotifierを継承することでnotifyListenersを使用することができるようになっている

`notifyListeners`
リスナーに変更通知を行う

`ChangeNotifierProvider`,`MultiProvider`
`ChangeNotifier`の状態を管理し、子Widgetに継承できる
複数の状態を監視する場合は`MultiProvider`を使用する

### ChangeNotifierの読み取り方法

Providerで状態として管理している値（context）を読み取る方法は次の3種類が提供さている

| メソッド        | 概要                                   | 再ビルド   | 使用シーン                                        |
|-----------------|----------------------------------------|------------|--------------------------------------------------|
| `context.read`  | 状態を一度だけ読み取る                | なし       | 状態を一度だけ取得したい場合、再ビルド不要な処理  |
| `context.watch` | 状態を監視し、変更時に再ビルドされる  | あり       | 状態が変わった時にウィジェットを再ビルドしたい場合 |
| `context.select`| 状態の一部を選択して監視し、変更時に再ビルド | あり       | 状態全体ではなく、特定の部分を監視したい場合      |


# まとめ

`Stream`:作成したStreamにデータを渡す
`Provider`:親子関係のWidget間でデータを渡す

それぞれ機能がまだあるのでもっと深掘りしていきたい