【Flutter/Dart】非同期処理Futureクラスとasync/awaitの使い方

最終更新日

Dartの基本文である非同期処理Futureクラスとasync/awaitの使い方についてまとめました。

動かすにはDartpadを使用してください。

非同期処理の基礎

はじめに

非同期処理・Futureについて

Jqueryをやっていた人なら非同期処理を理解しやすいかもしれません。

abcの処理があり、bの結果を待たずacに移行するといった感じです。

要は「後で戻り値返すから、とりあえず進めといて」って感じです。

futureとは「Future型」のインスタンスです。
  • Uncompleted:非同期処理が完了していない状態
  • Completed :非同期処理が完了している状態(結果「value」またはエラーを返す)
futureには「Uncompleted (処理中)」「Completed (処理完了)」の2つの状態があります。 非同期処理が完了した際に結果を返すのは「Future」、処理だけ実行するのは「Future」です。

非同期処理のポイントは以下のようである:

  • Dartのコードは単一の実行「スレッド」のなかで走る
  • この実行スレッドを妨害するようなコードがあれば、そのプログラムは止まったまま(フリーズ状態)となる
  • Futureオブジェクトたち(futures)は非同期操作の結果を表現している – 処理或いはI/Oは後で完了する
  • あるfutureが完了するまで処理を保留するには、async関数のなかでawaitを使う(あるいはthen()を使う)
  • エラーを捕捉するには、async関数の中でtry-catch 式を使う(或いはcatchError()を使う)
  • 並行処理でコードを走らせるにはアイソレートを作る(或いはウェブ・アプリの場合はworkerを使う)
https://www.cresc.co.jp/tech/java/Google_Dart2/language/asynchrony_futures/asynchrony_futures.html

サンプル①

サンプル② 

返り値を受けとる

サンプル①

解説

  1. createOrderMessage関数にfetchUserOrderの処理をorderに代入
  2. 2秒後、fetchUserOrder関数の結果が出るのでcreateOrderMessage関数のorderに代入(Large Latte)
  3. main関数でcreateOrderMessage関数の値とmain関数の値をまとめて出力させる

サンプル②

Futureの型

このようにFutureを使用するには、型を指定する必要があります。

Future<型>

処理の後は<>指定したものしか戻り値に指定できません

その後、「{}」の前に「async」を書き足します。

一方、関数内で非同期処理(future)を使用する場合は「await」を使います。(awaitはasyncを宣言した関数内でしか使用できません)

//void型
Future<void> future1() async {
  await future;
} 

//return型(Stringの場合)
Future<String> future2() async {
  await future;
  return 戻り値;
}

Futureのコンストラクタ

公式サイト

https://api.dart.dev/stable/2.16.1/dart-async/Future-class.html

①delayed

使用例 

表示するまでの時間を指定します。{}の中に処理を書きます。

Future.delayed(Duration(時間単位: 時間指定), (){
  処理...
});
サンプル① 

まずは、一番簡単なFutureを使ったサンプルを用意してみました。

エラー

Error compiling to JavaScript: Info: Compiling with sound null safety lib/main.dart:7:16: Error: A non-null value must be returned since the return type ‘String’ doesn’t allow null. Future<String> hello() async { ^ Error: Compilation failed.

JavaScriptへのコンパイルに失敗しました。
情報 NULLセーフティでコンパイルしています
lib/main.dart:7:16:
エラーです。戻り値の型 ‘String’ は null を許さないので、null でない値を返す必要があります。
Future hello() async {。
^
エラー。コンパイルに失敗しました。

①then

この処理が完了したときに呼び出すコールバック関数を指定します。

サンプル①

サンプル

こちらは、Flutterのcookbockにあるサンプルです。初めてやるものにしては少し難しいかもしれませんが、少しずつ理解していきましょう!

解説

当サイトでも少し解説してみます。

  1. パッケージを追加
  2. FutureクラスでJosnをネットから引っ張ってくる
  3. Alubmクラスを作る
  4. http.ResponseをAlubmクラスに変換
  5. データの取得
  6. データの表示
①パッケージを追加

httpパッケージをyamlファイルに追加します。

追加する方法は以下の記事を参照してください。

公式サイト

https://docs.flutter.dev/cookbook/networking/fetch-data

import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

Future<Album> fetchAlbum() async {
  final response = await http
      .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));

  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then parse the JSON.
    return Album.fromJson(jsonDecode(response.body));
  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to load album');
  }
}

class Album {
  final int userId;
  final int id;
  final String title;

  const Album({
    required this.userId,
    required this.id,
    required this.title,
  });

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
    );
  }
}

void main() => runApp(const MyApp());

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late Future<Album> futureAlbum;

  @override
  void initState() {
    super.initState();
    futureAlbum = fetchAlbum();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Fetch Data Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Fetch Data Example'),
        ),
        body: Center(
          child: FutureBuilder<Album>(
            future: futureAlbum,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return Text(snapshot.data!.title);
              } else if (snapshot.hasError) {
                return Text('${snapshot.error}');
              }

              // By default, show a loading spinner.
              return const CircularProgressIndicator();
            },
          ),
        ),
      ),
    );
  }
}

参考

https://www.cresc.co.jp/tech/java/Google_Dart2/language/asynchrony_futures/asynchrony_futures.htm

https://medium.com/@kawanojieee/dart-async-await-394846fb3d2c

https://zenn.dev/urasan/articles/f6613470658de1

https://zenn.dev/tomofyro/articles/3e5eef4a2c3857

https://gist.github.com/search?q=async+language%3ADart

https://gist.github.com/search?q=Future+language%3ADart&ref=searchresults