🎉

【Flutter】Dartの非同期処理(Future・then)

2023/08/21に公開

はじめに

Flutterでアプリ開発を行うには、Dart言語の使い方を理解しておく必要があります。今回は、Dart言語の非同期処理について説明します。非同期処理は、WebAPIを叩いたり、DBやファイルの読み書き等、時間のかかる処理を行う際に使用します。

動作環境

Flutterで新規プロジェクトを作成し、以下のここに処理を書いていくと書かれた2箇所に追加でコードを記述して動作確認していきます。画面にボタンを表示する単純なプログラムです。

import 'dart:io';

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

+ // ここに処理を書いていく

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text("Hello World")),
        body: Center(
          child: ElevatedButton(
            child: Text("ボタン"),
            onPressed: () {
+             // ここに処理を書いていく
            },
          )
        ),
      ),
    );
  }
}

以下のような画面が表示されます。

Flutterの非同期処理

Flutterの非同期処理は、以下の2つの方法があります。今回は、理解が難しいFuture・thenを使用する方法を説明します。

  • Future・thenを使用する方法
  • async・awaitを使用する方法

非同期処理とは

まず、非同期処理とは何か、以下のプログラムを示します。プログラムを実行して、画面に表示されるボタンをクリックしてください。

import 'dart:io';

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

   // ここに処理を書いていく
+  void task1() {
+   print("task1");
+  }
+
+  void task2() {
+   // 3秒待つ処理の後にprintを実行
+   sleep(Duration(seconds: 3));
+   print("task2: 3秒経ちました");
+  }
+
+  void task3() {
+   print("task3");
+  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text("Hello World")),
        body: Center(
          child: ElevatedButton(
            child: Text("ボタン"),
            onPressed: () {
              // ここに処理を書いていく
+             task1();
+             task2();
+             task3();
            },
          )
        ),
      ),
    );
  }
}

出力結果は以下の通りになります。

flutter: task1
flutter: task2: 3秒経ちました
flutter: task3
  • task1出力→3秒後→task2出力→task3出力となります
  • このように、順に処理を実行することを同期処理と言います
  • 時間のかかる処理の完了を待つと、次の処理が止まってしまいます。時間のかかる処理は非同期処理で実装します
  • 非同期処理は、処理の完了を待つ事なく次の処理を実行(並列に処理)します

上のプログラムで、時間のかかる部分を非同期処理に変更してみます。

import 'dart:io';

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

   // ここに処理を書いていく
   void task1() {
     print("task1");
   }

   void task2() {
     sleep(Duration(seconds: 3));
   }

   void task3() {
     print("task3");
   }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text("Hello World")),
        body: Center(
          child: ElevatedButton(
            child: Text("ボタン"),
            onPressed: () {
              // ここに処理を書いていく
              task1();
+             Future(() {
+               task2();
+             },).then((value) {
+               print("task2: 3秒経ちました");
+             },);
              task3();
            },
          )
        ),
      ),
    );
  }
}

出力結果は以下の通りになります。

flutter: task1
flutter: task3
flutter: task2: 3秒経ちました
  • task1出力→task3出力→3秒後→task2出力となります
  • task2の終了を待つ事なく、task3を実行しています
  • Futureにより、時間がかかる部分を非同期処理で実装できました

それでは、以降で非同期処理の詳細について説明します。

非同期処理の詳細

Future とは

  • Futureは、非同期処理の実行完了後の未来において、実行結果を格納するための箱のようなものです。
  • Futureは複数の状態を持ちます
    • 非同期処理が未完了の状態
    • 非同期処理が完了の状態(実行結果を格納している)

Futureの処理の流れ

Futureの上記の2つの状態を、以下のプログラムで具体的に見てみましょう。

import 'dart:io';

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

   // ここに処理を書いていく

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text("Hello World")),
        body: Center(
          child: ElevatedButton(
            child: Text("ボタン"),
            onPressed: () {
              // ここに処理を書いていく
+              var myFuture = Future(() {
+                sleep(Duration(seconds: 5));
+                return 0;
+              }).then((value) {
+                print("完了");
+               print(value);
+              });
+              print("未完了");
+              print(myFuture);

            },
          )
        ),
      ),
    );
  }
}

出力結果は以下の通りになります。

flutter: 未完了
flutter: Instance of 'Future<Null>'
flutter: 完了
flutter: 0
  • Future内は非同期処理のため、Future後の処理が先に出力されています
  • 非同期処理が未完了の状態では、Futureの返り値(myFuture変数)はNullとなっています。処理が完了すると、returnの値である0が格納されています

Future・thenの書き方

次に、先ほどのプログラムを詳しく見てみましょう。

var myFuture = Future(() {
  sleep(Duration(seconds: 5));
  return 0;
}).then((value) {
  print("完了");
  print(value);
});
print("未完了");
print(myFuture);

Futureを使用した非同期処理は以下のように記述します。

Future((){ 
  // 時間のかかる処理
});

また、Futureの実行結果(返り値)を得るには以下のようにします。

var myFuture = Future(() {
  // 時間のかかる処理
  return 返り値
}).then((value) {
  // returnされた返り値がvalueに格納され、then以下が実行される
  print(value);
});
  • myFuture、return、thenを追加しています
  • returnの後(非同期処理終了後)、返り値がvalueに格納され、then以下が実行されます

Futureの実用例

これまで、Futureを自分自身で作って非同期処理を行いました。しかし、Flutterにおける非同期処理では、Futureを自分自身で作ることはほとんどありません。非同期処理のライブラリはFutureを考慮して作成されているためです。

実際に、一般的な非同期処理を試してみましょう。HTTP通信を行ってみます。まず、HTTP通信に必要なライブラリをインストールします。

flutter pub add dio

HTTP通信で、Web APIを叩いてみようと思います。今回使用するWeb APIは、httpbinです。登録など一切不要で、Web APIを使用した動作確認を行いたいときに手軽に使う事ができます。

以下のプログラムを実行してみましょう。httpbinを叩き、ipアドレスを取得して出力します。
※うまく動作しない場合は、実機でデバッグするようにしてください。

import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

   // ここに処理を書いていく
   
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text("Hello World")),
        body: Center(
          child: ElevatedButton(
            child: Text("ボタン"),
            onPressed: () {
              // ここに処理を書いていく
+             var dio = Dio();
+             var response = dio.get("http://httpbin.org/ip");
+             print(response);
+             response.then((value) {
+               print(value.data);
+             });
            },
          )
        ),
      ),
    );
  }
}

出力結果は以下の通りになります。

{origin: xx.xx.xx.xx}

では、プログラムを解説します。

var dio = Dio();
// 時間のかかる処理(HTTP通信)
// responseはFuture型のため、非同期処理が終わった後(thenの後)で値を取り出す
var response = dio.get("http://httpbin.org/ip");
// まだHTTP通信の結果は格納されていない
print(response);
response.then((value) {
  // HTTP通信の結果が格納されている
  print(value.data);
});
  • 時間のかかるHTTP通信はFutureの中で非同期処理しています
  • 非同期処理終了後、thenの中の処理を実行し、値を取り出します

非同期処理の典型例であるHTTP通信を試してみました。返り値のFutureのみthenを使って処理する事で、値を取得する事ができます。様々なライブラリをFuture・thenを使って試してみて下さい。

おわりに

Flutterの非同期処理(Future・thenを使用した方法)を説明しました。Futureの詳しい解説は、以下の動画がわかりやすいです。是非、動画を見てFutureについて理解を深めてみてください。

Discussion