データを収集するためにHTTP呼び出しが必要なウィジェットをレンダリングしたいと思います。
次のコードを取得しました(簡略化)
import 'package:flutter/material.Dart';
import 'Dart:async';
import 'Dart:convert';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'async demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var asyncWidget;
@override
initState() {
super.initState();
loadData().then((result) {
print(result);
setState(() {
asyncWidget = result;
});
});
}
loadData() async {
var widget = new AsyncWidget();
return widget.build();
}
@override
Widget build(BuildContext context) {
if(asyncWidget == null) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Loading..."),
),
);
} else {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: this.asyncWidget,
),
);
}
}
}
class MyRenderer {
MyRenderer();
Widget render (List data) {
List<Widget> renderedWidgets = new List<Widget>();
data.forEach((element) {
renderedWidgets.add(new ListTile(
title: new Text("one element"),
subtitle: new Text(element.toString()),
));
});
var lv = new ListView(
children: renderedWidgets,
);
return lv;
}
}
class MyCollector {
Future gather() async {
var response = await // do the http request here;
return response.body;
}
}
class AsyncWidget {
MyCollector collector;
MyRenderer renderer;
AsyncWidget() {
this.collector = new MyCollector();
this.renderer = new MyRenderer();
}
Widget build() {
var data = this.collector.gather();
data.then((response) {
var responseObject = JSON.decode(response);
print(response);
return this.renderer.render(responseObject);
});
data.catchError((error) {
return new Text("Oups");
});
}
}
私のコードは次のように動作します:非同期データを使用するウィジェットは、コレクター(http呼び出しを行う)と、httpデータでウィジェットをレンダリングするレンダラーを取ります。 initState()でこのウィジェットのインスタンスを作成し、非同期呼び出しを行います。
SetState()メソッドを使用してウィジェットを新しいデータで更新する必要があるというドキュメントを見つけましたが、これはうまくいきません。
ただし、いくつかのログを置くと、HTTP呼び出しが行われ、setState()メソッドが呼び出されますが、ウィジェットは更新されません。
これを行う最良の方法は、 FutureBuilder を使用することです。
FutureBuilderドキュメントから:
new FutureBuilder<String>(
future: _calculation, // a Future<String> or null
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none: return new Text('Press button to start');
case ConnectionState.waiting: return new Text('Awaiting result...');
default:
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
else
return new Text('Result: ${snapshot.data}');
}
},
)
もう1つは、State.buildメソッドの外でウィジェットを構築し、ウィジェット自体を保存するということです。これはアンチパターンです。 buildメソッドで毎回実際にウィジェットを構築する必要があります。
FutureBuilderを使用せずにこれを機能させることもできますが、http呼び出しの結果を保存し(適切に処理)、ビルド関数内でデータを使用する必要があります。
これを参照してください。ただし、FutureBuilderを使用する方がより適切な方法であり、学習のためにこれを提供しているだけです。
import 'Dart:async';
import 'Dart:convert';
import 'package:flutter/material.Dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'async demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List data;
@override
initState() {
super.initState();
new Future<String>.delayed(new Duration(seconds: 5), () => '["123", "456", "789"]').then((String value) {
setState(() {
data = json.decode(value);
});
});
}
@override
Widget build(BuildContext context) {
if (data == null) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Loading..."),
),
);
} else {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new ListView(
children: data
.map((data) => new ListTile(
title: new Text("one element"),
subtitle: new Text(data),
))
.toList(),
),
),
);
}
}
}