153
108

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Flutter] FutureBuilderを使って非同期でWidgetを生成する

Last updated at Posted at 2019-01-10

はじめに

最近Flutterはじめました。
FutureBuilderはとてもシンプルで使いやすい機能ですが、初見で何やってるのかピンとこなかったのでまとめます。

2019/12/15 追記: かなり雑だったので少し修正しました。

まずはFutureBuilderを使って表示するためのデータを返してくれるメソッドをテスト用に作成しましょう。
_getFutureValue()です、APIで通信して文字列を取得するケースを擬似的に再現しています。
1秒後にFuture型の"データの取得に成功しました"という文字列を返します。

  Future<String> _getFutureValue() async {
    // 擬似的に通信中を表現するために1秒遅らせる
    await Future.delayed(
      Duration(seconds: 1),
    );
    return Future.value("データの取得に成功しました");
  }

FutureBuilderを追加 

次は本来表示したいWidgetの階層にFutureBuilderを追加します、今回はCenter直下です。

futureの引数に先程作った_getFutureValue()を代入、
builderには(BuildContext context, AsyncSnapshot snapshot) { }

futureにセットしたメソッドがreturnしてくれる値(今回の場合は"データの取得に成功しました")をsnapshot.dataで取得できます。

しかし、ただbuilder内でreturn Text(snapshot.data);としてしまうとエラーになってしまいます。
最初の1秒間は_getFutureValue()は何もデータを返さないのでsnapshot.dataがnulになってしまうからです。

snapshotに本当にデータが格納されているかをsnapshot.hasDataで確認してからreturnしましょう。

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('FutureBuilder Demo'),
      ),
      body: Center(
        child: FutureBuilder(
          future: _getFutureValue(),
          builder: (BuildContext context, AsyncSnapshot<String> snapshot) { 
            if (snapshot.hasData) {
              return Text(snapshot.data);
            } else {
              return Text("データが存在しません");
            }
          },
        ),
      ),
    );
  }

builderを(BuildContext context, AsyncSnapshot<指定したい型> snapshot)とすればsnapshot.dataの型を指定できます。

通信状態を考慮する

上記のケースだとデータがあるかないかの分岐しかないので
通信中でデータがない場合でも、通信完了したがデータの取得には失敗した場合でも"データが存在しません"とだけ表示されてしまいます。

通信が完了するまではスピナーを表示させましょう。
通信状態はsnapshot.connectionStateで取得できます
ConnectionStateがdoneになるまではCircularProgressIndicatorを表示させます。

            if (snapshot.connectionState != ConnectionState.done) {
              return CircularProgressIndicator();
            }

エラーハンドリング

通信に失敗した場合などを想定しましょう。

まずは先程作った_getFutureValue()でExceptionを返すように修正します


  Future<String> _getFutureValue() async {
    // 擬似的に通信中を表現するために1秒遅らせる
    await Future.delayed(
      Duration(seconds: 1),
    );

    try {
      // 必ずエラーを発生させる
    throw Exception("データの取得に失敗しました");
    } catch (error) {
      return Future.error(error);
    }
  }

エラーかどうかはsnapshot.hasErrorで、
エラーメッセージはsnapshot.errorで取得できます。
エラーの場合はエラーメッセージを表示させるようにしましょう。


            if (snapshot.hasError) {
              return Text(snapshot.error.toString());
            }

ここまで全ての実装でbuildメソッド全体はこのようになります

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('FutureBuilder Demo'),
      ),
      body: Center(
        child: FutureBuilder(
          future: _getFutureValue(),
          builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
            // 通信中はスピナーを表示
            if (snapshot.connectionState != ConnectionState.done) {
              return CircularProgressIndicator();
            }

            // エラー発生時はエラーメッセージを表示
            if (snapshot.hasError) {
              return Text(snapshot.error.toString());
            }

            // データがnullでないかチェック
            if (snapshot.hasData) {
              return Text(snapshot.data);
            } else {
              return Text("データが存在しません");
            }
          },
        ),
      ),
    );
  }


さいごに

正直Dart良くわかってない感まだあるので間違ってたらぜひおしえてください。

コード全体はこちらです

153
108
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
153
108

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?