我想渲染一个需要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()方法,但是小部件不会更新。
答案 0 :(得分:15)
执行此操作的最佳方法是使用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}');
}
},
)
另一件事是你在State.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(),
),
),
);
}
}
}
答案 1 :(得分:2)
最佳方式用于异步调用后的rander小部件正在使用 FutureBuilder()
class _DemoState extends State<Demo> {
@override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: downloadData(), // function where you call your api
builder: (BuildContext context, AsyncSnapshot<String> snapshot) { // AsyncSnapshot<Your object type>
if( snapshot.connectionState == ConnectionState.waiting){
return Center(child: Text('Please wait its loading...'));
}else{
if (snapshot.hasError)
return Center(child: Text('Error: ${snapshot.error}'));
else
return Center(child: new Text('${snapshot.data}')); // snapshot.data :- get your object which is pass from your downloadData() function
}
},
);
}
Future<String> downloadData()async{
// var response = await http.get('https://getProjectList');
return Future.value("Data download successfully"); // return your response
}
}
在将来的构建器中,它调用future函数以等待结果,并在生成结果后立即在构建小部件的地方调用构建器函数。
AsyncSnapshot具有3种状态:
1. connectionState.none =在此状态下,将来为空
2. connectionState.waiting = [未来]不为空,但尚未完成
3. connectionState.done = [future]不为null,并且已完成。如果将来成功完成,则[AsyncSnapshot.data]将设置为将来完成的值。如果完成并出现错误,则[AsyncSnapshot.hasError]为true
答案 2 :(得分:-1)
添加到@rmtmckenzie接受的答案,这对于处理发生网络错误时的情况很重要,因为snapshot.data
将拥有数据,但错误将出现在snapshot.data.exception
中,所以
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}');
if (snapshot?.data?.exception?.clientException
is NetworkException) {
return new
Text('Result:${snapshot..data?.exception?.clientException?.message}');
}
else
return new Text('Result: ${snapshot.data}');
}
},
)