Flutterでネットワーク通信やデータベースアクセスなど、時間のかかる処理を扱う時、必ず「非同期プログラミング」という考え方が必要になります。
このドキュメントでは、Future、async、awaitという3つのキーワードを中心に、非同期プログラミングの基本を、レストランのウェイターの仕事に例えて解説します。
Dart(Flutterが使う言語)は、基本的にシングルスレッドで動作します。これは、「一度に一つの作業しかできない」ということです。
もし、すべての処理を「同期的(順番通り)」に行うとどうなるでしょうか?
- 同期的なウェイターの仕事:
- お客さんAから注文を取る。
- キッチンに注文を伝え、料理が完成するまでその場でずっと待ち続ける。
- 料理が完成したら、お客さんAに運ぶ。
- ようやく、次のお客さんBの注文を取りに行く。
このウェイターは、キッチンの作業が終わるまで他の仕事を一切できません。その間、お客さんB、C、D...は、注文すらできずにイライラして待つことになります。
Flutterアプリも同じです。もし、重いネットワーク通信(料理)が終わるまでUIの更新(次の注文)をブロックしてしまうと、**アプリは完全にフリーズしてしまいます。**これではユーザー体験は最悪です。
この問題を解決するのが非同期プログラミングです。
- 非同期的な(優秀な)ウェイターの仕事:
- お客さんAから注文を取る。
- キッチンに注文を伝え、「料理ができたら教えてね」とだけ言って、その場を離れる。
- キッチンの待ち時間に、お客さんBの注文を取ったり、お客さんCに水を運んだりする。
- キッチンから「料理できましたよ!」と声がかかったら、料理を受け取ってお客さんAに運ぶ。
このウェイターは、待ち時間を他の作業に充てることで、レストラン全体を効率的に回しています。アプリのUIもフリーズすることなく、サクサク動き続けます。
Dartでは、この「優秀なウェイターの仕事」を、Future、async、awaitという3つのキーワードで実現します。
Futureは、時間のかかる非同期処理の結果を表すオブジェクトです。
- これは、レストランで注文した時にもらえる「料理の引換券」のようなものです。
- 注文した瞬間(非同期処理を開始した瞬間)に、すぐにこの
Future(引換券)が手渡されます。 - この引換券は、現時点ではまだ料理そのものではありませんが、「未来に、この引換券と引き換えに、料理(本当の結果)が手に入りますよ」という約束を表しています。
Future<String>なら「未来にString型のデータが手に入る引換券」、Future<int>なら「未来にint型のデータが手に入る引換券」という意味になります。
// ネットワークからユーザー名を取得する(かもしれない)関数
Future<String> fetchUserName() {
// 3秒後に 'Taro' という名前を返す処理をシミュレート
return Future.delayed(Duration(seconds: 3), () => 'Taro');
}このFuture(引換券)を、簡単かつ直感的に扱うためのシンタックスシュガー(便利な書き方)がasyncとawaitです。
-
async(アシンク):- 関数の定義の後ろに
asyncと付けると、その関数は「非同期的な作業を含む、優秀なウェイターの関数ですよ」という宣言になります。 asyncが付いた関数は、必ず戻り値がFutureでラップされます(StringはFuture<String>になる)。
- 関数の定義の後ろに
-
await(アウェイト):async関数の中でしか使えません。Futureオブジェクトの前にawaitを付けると、「このFuture(引換券)が、本物の結果(料理)に変わるまで、ここで一旦処理を中断して、他の仕事をどうぞ」という意味になります。- 処理は中断しますが、アプリ(UIスレッド)はフリーズしません。
- そして、
Futureの結果が返ってきたら、そこから処理を再開します。
// ユーザー名を取得して、挨拶を表示する関数
Future<void> greetUser() async { // 1. この関数は非同期処理を含む宣言 (async)
print('ユーザー名を取得します...');
// 2. fetchUserName()が完了するまで、ここで待つ (await)
// でも、アプリはフリーズしない!
String userName = await fetchUserName();
// 3. 上のawaitが完了した後、この行が実行される
print('こんにちは, $userName さん!');
}
void main() {
greetUser();
print('greetUser()の呼び出しは完了しました。結果を待っています...');
}実行結果:
ユーザー名を取得します...
greetUser()の呼び出しは完了しました。結果を待っています...
(3秒後)
こんにちは, Taro さん!
greetUser()を呼び出した後、すぐに次のprint文が実行されていることに注目してください。これが、アプリがフリーズしない証拠です。
非同期プログラミングは、AIとのAPI連携やデータベース操作など、現代のアプリ開発に不可欠なスキルです。この「ウェイターと引換券」のイメージを掴めば、きっとマスターできるはずです。