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