Flutter

【Flutter】StateにFutureを持たせた時のreload方法

最終更新から6年以上たっています。ライブラリのバージョンや手順が変わっている可能性があります。

こんにちは。趣味グラマのNobu(@nm_aru)です。

今回もFlutterの備忘録を残しておこうと思います。

StateのFutureをreloadする(失敗例)

build内でFutureBuilderを使い、そこで使用するデータとしてFuture<List<String>>みたいな物をStatefulWidgetのStateとして保持していました。

内容は変えていますが、以下のようなソースコードです。

ListViewWidgetBefore.dart
import 'package:flutter/material.dart';

class ListViewWidget extends StatefulWidget {
  @override
  _ListViewWidgetState createState() => _ListViewWidgetState();
}

class _ListViewWidgetState extends State<ListViewWidget> {
  Future<List<String>> _data;

  @override
  void initState() {
    super.initState();
    _data = HogeData().getHogeData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('List'),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.swap_vert),
        onPressed: () => _reload(),
      ),
      body: FutureBuilder<List<String>>(
          future: _data,
          builder: (context, snapshot) {
            switch (snapshot.connectionState) {
              case ConnectionState.none:
                break;
              case ConnectionState.active:
              case ConnectionState.waiting:
                return Center(child: SizedBox(height: 50.0, width: 50.0, child: CircularProgressIndicator()));
              case ConnectionState.done:
                if (snapshot.hasError) {
                  return Text('${snapshot.error}');
                }

                if (snapshot.hasData) {
                  return ListView.builder(
                    itemCount: snapshot.data.length,
                    itemBuilder: (context, index) => ListItem(snapshot.data[index]),
                  );
                }
            }
            return null;
          }),
    );
  }

  void _reload() {
    var data = HogeData().getHogeData();
    setState(() => _data = data); // ★★ これだとエラーになる
  }
}

この状況で、FloatingActionButtonボタンを押すと、55行目で以下のエラーが発生します。

FlutterError (setState() callback argument returned a Future.
The setState() method on _ListViewWidgetState#3ce67 was called with a closure or method that returned a Future. Maybe it is marked as “async”.
Instead of performing asynchronous work inside a call to setState(), first execute the work (without updating the widget state), and then synchronously update the state inside a call to setState().)

エラーメッセージを見る限り、setState()の中で非同期のFutureを使っている事が問題のように見えます。

しかし、StateにFutureを持っているので、setStateを呼ばないとreloadされない…さてどうしたものかと色々試した結果、次のコードで無事解決しました。

StateのFutureをreloadする(成功例)

ListViewWidgetAfter.dart
import 'package:flutter/material.dart';

class ListViewWidget extends StatefulWidget {
  @override
  _ListViewWidgetState createState() => _ListViewWidgetState();
}

class _ListViewWidgetState extends State<ListViewWidget> {
  Future<List<String>> _data;

  @override
  void initState() {
    super.initState();
    _data = HogeData().getHogeData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('List'),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.swap_vert),
        onPressed: () => _reload(),
      ),
      body: FutureBuilder<List<String>>(
          future: _data,
          builder: (context, snapshot) {
            switch (snapshot.connectionState) {
              case ConnectionState.none:
                break;
              case ConnectionState.active:
              case ConnectionState.waiting:
                return Center(child: SizedBox(height: 50.0, width: 50.0, child: CircularProgressIndicator()));
              case ConnectionState.done:
                if (snapshot.hasError) {
                  return Text('${snapshot.error}');
                }

                if (snapshot.hasData) {
                  return ListView.builder(
                    itemCount: snapshot.data.length,
                    itemBuilder: (context, index) => ListItem(snapshot.data[index]),
                  );
                }
            }
            return null;
          }),
    );
  }

  void _reload() {
    _data = HogeData().getHogeData();
    setState(() {}); // ★これ!
  }
}

変更したのは54〜55行目になります。

要はStateのFutureに直接データを入れたあと、setState()をただ呼び出すだけです。

まとめ

この解決法がFlutter的に良いのかは分かりませんが、趣味グラマにとってはコードが動く事がまずは大事という事で、ひとまず解決して良かったです(^^)

made with Flutter

この記事を書いた人がつくったアプリ

Flutter の記事

コメントの受け付けは終了しました。ご意見・ご質問は X(@nm_aru)へどうぞ。