背景
Flutterでアプリ開発を行った際、非同期処理や状態管理にStreamとProviderを知ったが違いがイマイチわからない。
それぞれどんな特徴があるかまとめる。
Stream
直訳すると「流れ」
宣言したStream上に「値」を流すイメージ
-
sink.add
で値をStreamに流し -
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にデータが流れてきた時、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
親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間でデータを渡す
それぞれ機能がまだあるのでもっと深掘りしていきたい