Flutter,在异步调用后呈现小部件

时间:2018-04-19 21:01:09

标签: async-await dart flutter

我想渲染一个需要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()方法,但是小部件不会更新。

3 个答案:

答案 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}');
  }
 },
)