0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

背景

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

Stream

直訳すると「流れ」
宣言した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

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

Streamに応じて画面の再描画を行えるWidget
基本構造は以下のコード
stream:監視するStreamを指定
initialData:Streamからデータが届くまでの初期値
builder:Streamにデータが流れてきた時、BuildContextAsyncSnapshot<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

親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間でデータを渡す

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?