お題
ポップアップで選択して、選択結果を表示する画面を考えます。
完成イメージ
StatefulWidget
とsetState
を使う
class HomeWidget extends StatefulWidget {
@override
HomeState createState() => HomeState();
}
class HomeState extends State<HomeWidget> {
TimeOfDay _time;
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(width: 3),
),
),
child: Text(
_time == null ? "未選択" : _time.format(context),
style: TextStyle(fontSize: 50),
),
),
RaisedButton(
child: Text(
"時刻を選択する",
style: TextStyle(fontSize: 20),
),
color: Colors.deepOrangeAccent,
onPressed: () async {
final select = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
this.setState(() => _time = select);
},
),
],
);
}
}
この例だと、ボタンを押して時刻を選択すると、テキストだけではなく、ボタンも再描画されています。
StreamBuilder
を使う
Stream?
Streamのイメージは、蛇口だ。
sink
にデータを流すと、stream
に流れるので、listen
で捕まえて処理するのだ。
StreamBuilderを使って書き換えよう
class HomeWidget extends StatefulWidget {
@override
HomeState createState() => HomeState();
}
class HomeState extends State<HomeWidget> {
final _onTimeChange = StreamController<TimeOfDay>();
@override
void dispose() {
// StreamControllerは必ず開放する
_onTimeChange.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// 2つのWidgetは親のもつStreamControllerのstreamとsinkを使ってデータの受け渡しを行う
TimeText(stream: _onTimeChange.stream),
TimeSelector(sink: _onTimeChange.sink),
],
);
}
}
/// 時刻を受け取って表示する
class TimeText extends StatelessWidget {
/// 受け口
final Stream<TimeOfDay> stream;
TimeText({this.stream});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(width: 3),
),
),
child: StreamBuilder(
// 指定したstreamにデータが流れてくると再描画される
stream: this.stream,
builder: (BuildContext context, AsyncSnapshot<TimeOfDay> snapShot) {
// StreamControllerから流れてきたデータを使って再描画
return Text(
snapShot.hasData ? snapShot.data.format(context) : "未選択",
style: TextStyle(fontSize: 50),
);
},
),
);
}
}
/// 時刻を選択して、選択結果を渡す
class TimeSelector extends StatelessWidget {
/// 渡し口
final StreamSink<TimeOfDay> sink;
TimeSelector({this.sink});
@override
Widget build(BuildContext context) {
return RaisedButton(
child: const Text(
"時刻を選択する",
style: TextStyle(fontSize: 20),
),
color: Colors.deepOrangeAccent,
onPressed: () async {
final select = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
// sinkに選択した時刻を流す
this.sink.add(select);
},
);
}
}
StreamBuilderは指定したstream
にデータが流れてくると、自動で再描画が実行されます。
今回の例だと、時刻を選択すると、選択したデータ<TimeOfDay>
がsink
を通して流れ込み、StreamBuilder
のsnapShot.data
に流れ着きます。
ボタンやその他のWidgetを再描画することなく、テキストだけを再描画することができます。
また、ボタンからテキストへはStreamController
を通してデータの受け渡しがされているため、
StreamController
さえ両方のWidgetに渡してしまえば、うまくデータのやり取りが可能です。
こうすることで、無駄な再描画を抑えつつ、状態管理の煩雑さから開放され、各Widgetをうまくパーツ化、再利用することができるようになります。
みんなもStreamBuilderをうまく使って快適なFlutterライフを!!!!