【Flutter】Dartの非同期処理（Future・then）

## はじめに

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

## 動作環境

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


```diff
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: () {
+             // ここに処理を書いていく
            },
          )
        ),
      ),
    );
  }
}
```
以下のような画面が表示されます。

![](https://storage.googleapis.com/zenn-user-upload/cf0241b04037-20230821.png)

## Flutterの非同期処理

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

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

## 非同期処理とは

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

```diff
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();
            },
          )
        ),
      ),
    );
  }
}

```

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

```shell
flutter: task1
flutter: task2: 3秒経ちました
flutter: task3
```

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

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

```diff
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();
            },
          )
        ),
      ),
    );
  }
}

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

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

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

## 非同期処理の詳細

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

### Futureの処理の流れ

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

```diff
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);

            },
          )
        ),
      ),
    );
  }
}

```

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

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

### Future・thenの書き方

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

```dart
var myFuture = Future(() {
  sleep(Duration(seconds: 5));
  return 0;
}).then((value) {
  print("完了");
  print(value);
});
print("未完了");
print(myFuture);
```
Futureを使用した非同期処理は以下のように記述します。

```dart
Future((){ 
  // 時間のかかる処理
});
```
また、Futureの実行結果（返り値）を得るには以下のようにします。

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

### Futureの実用例

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

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

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

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

```diff
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);
+             });
            },
          )
        ),
      ),
    );
  }
}
```
出力結果は以下の通りになります。

```shell
{origin: xx.xx.xx.xx}
```
では、プログラムを解説します。

```dart
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について理解を深めてみてください。

- https://www.youtube.com/watch?v=OTS-ap9_aXc