web-dev-qa-db-ja.com

フラッター、非同期呼び出し後にウィジェットをレンダリング

データを収集するために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()メソッドが呼び出されますが、ウィジェットは更新されません。

21
Alexi Coard

これを行う最良の方法は、 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(),
          ),
        ),
      );
    }
  }
}
31
rmtmckenzie