Flutterで非同期をかっこよく扱う
目次
- 目次
- 1. 【チートシート】FutureBuilder と StreamBuilderの比較
- 2. Async/AwaitでのFutureの処理
- 3. FutureBuilder の使い方
- 4. StreamBuilder の使い方
- まとめ
1. 【チートシート】FutureBuilder と StreamBuilderの比較
FutureBuilder
: Futureを返す処理を用いてウィジェットを作成するときに使用する。
Future<String> calculation;
FutureBuilder<String>(
future: calculation,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
List<Widget> children;
if (snapshot.hasData) {
children = <Widget>[
const Icon(
Icons.check_circle_outline,
color: Colors.green,
),
Text('Result: ${snapshot.data}'),
];
} else if (snapshot.hasError) {
children = <Widget>[
const Icon(
Icons.error_outline,
color: Colors.red,
),
Text('Error: ${snapshot.error}'),
];
} else {
children = const <Widget>[
CircularProgressIndicator(),
Text('Awaiting result...'),
];
}
return Column(
children: children,
);
},
)
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
StreamBuilder
: Streamを返す処理を用いてウィジェットを作成するときに使用する。Stream値が変化した時に再描画される。
Stream<int> bids; // ストリーム
StreamBuilder<int>(
stream: bids,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
List<Widget> children;
if (snapshot.hasError) {
// エラーが発生した場合
children = <Widget>[
const Icon(
Icons.error_outline,
color: Colors.red,
),
Text('Error: ${snapshot.error}'),
Text('Stack trace: ${snapshot.stackTrace}'),
];
} else {
switch (snapshot.connectionState) {
case ConnectionState.none:
children = const <Widget>[
Icon(
Icons.info,
color: Colors.blue,
),
Text('Select a lot'),
];
break;
case ConnectionState.waiting:
children = const <Widget>[
CircularProgressIndicator(),
Text('Awaiting bids...'),
];
break;
case ConnectionState.active:
children = <Widget>[
const Icon(
Icons.check_circle_outline,
color: Colors.green,
),
Text('\$${snapshot.data}'),
];
break;
case ConnectionState.done:
children = <Widget>[
const Icon(
Icons.info,
color: Colors.blue,
),
Text('\$${snapshot.data} (closed)'),
];
break;
}
}
return Column(
children: children,
);
},
)
https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html
2. Async/AwaitでのFutureの処理
FirebaseやAPIをFlutter内で呼び出すときにはローディングなど、非同期処理を行う必要がある。
Async/Awaitを利用して非同期処理を行うとどのようになるだろう?
シンプルに書くと以下のようになるだろう。
Future<String> calculation() async {...};
class _MyHomePageState extends State<MyHomePage> {
_MyHomePageState({required this.eventId}) : super();
dynamic _calc = null;
// initStateで値を更新している
void initState() async {
super.initState();
setState(() {
_calc = await calculation();
});
}
Widget build(BuildContext context) {
if (_calc == null) {
// まだinitStateが完了していない
return const CircularProgressIndicator();
}
// 以下はinitStateが完了している場合
return Text("calc result is $_calc");
}
}
このくらいだったらまだ理解できるが、エラー処理や複数の非同期処理を扱うようになると可読性が悪くなる。
3. FutureBuilder の使い方
FutureBuilderは非同期が完了したあとに、Widgetを生成する。
setStateなどをコードに書く必要がなくなったため、可読性が増したと思う。
Future<String> calculation() async {...};
class _MyHomePageState extends State<MyHomePage> {
_MyHomePageState({required this.eventId}) : super();
Widget build(BuildContext context) {
// 以下はinitStateが完了している場合
return FutureBuilder(
future: calculation(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return Text("Error: ${snapshot.error}");
}
if (!snapshot.hasData) {
return Text("データが見つかりません");
}
// データ表示
return Text('${snapshot.data}');
} else {
// 処理中の表示
return const CircularProgressIndicator();
}
},
):
}
}
4. StreamBuilder の使い方
これらが本当に力を発揮するのはStreamBuilderの方だろう。
同じような記述でStreamを扱うことができる。
Stream<String> calculation() async {...};
class _MyHomePageState extends State<MyHomePage> {
_MyHomePageState({required this.eventId}) : super();
Widget build(BuildContext context) {
// 以下はinitStateが完了している場合
return StreamBuilder(
stream: calculation(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return Text("Error: ${snapshot.error}");
}
if (!snapshot.hasData) {
return Text("データが見つかりません");
}
// データ表示
return Text('${snapshot.data}');
} else {
// 処理中の表示
return const CircularProgressIndicator();
}
},
):
}
}
Streamが変化したときにリアルタイムで画面も変化するので、動的なUIを作成するのに有利だろう。
FireStoreではFirebaseFirestore.instance.collection('コレクション名').snapshots()
でストリームを取得できる。
実際の利用例は以下。
class _MyHomePageState extends State<MyHomePage> {
final Stream<QuerySnapshot<Map<String, dynamic>>> _eventListStream =
FirebaseFirestore.instance.collection('event-list').snapshots();
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
stream: _eventListStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('error:${snapshot.error}');
}
if (snapshot.connectionState == ConnectionState.waiting) {
// ローディング
return CircularProgressIndicator(
semanticsLabel: 'Linear progress indicator',
);
}
return ListView(
children: snapshot.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data = document.data() as Map<String, dynamic>;
return ListTile(
title: _messageItem('${data['title']}', document.id),
);
}).toList());
},
);
}
}
まとめ
Flutterでの非同期処理は、StreamBuilderやFutureBuilderを使うことで、可読性を上げるやり方を紹介した。
データの取得が一度でいい場合はFutureBuilderを使う。
動的にデータを更新する場合はStreamBuilderを使うとよいだろう。
Discussion