Futureをコンソールで試す
Futureは後で得られる結果を表します。asyncな関数の中でawaitすると完了まで次の処理を待ちます。これはネットワークではなく1秒の待ち時間を作る練習です。basics.dartへ保存し dart run basics.dart で実行します。
Future<String> loadLesson() async {
await Future<void>.delayed(const Duration(seconds: 1));
return 'Widget';
}
Future<void> main() async {
print('開始');
final lesson = await loadLesson();
print(lesson);
print('終了');
}開始
Widget
終了開始の後、約1秒待って残りを表示します。awaitは呼び出した処理の完了を待ちますが、重い計算を自動で別スレッドに移す機能ではありません。
3つの状態を画面に表示
lib/main.dartを次の内容に置き換え、flutter run -d chrome で実行します。成功と失敗のボタンで両方の経路を確かめられます。実際の通信や外部パッケージは使いません。
import 'package:flutter/material.dart';
void main() => runApp(const StudyApp());
class StudyApp extends StatelessWidget {
const StudyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: LoadPage());
}
}
class LoadPage extends StatefulWidget {
const LoadPage({super.key});
@override
State<LoadPage> createState() => _LoadPageState();
}
class _LoadPageState extends State<LoadPage> {
bool _loading = false;
String _message = 'まだ読み込んでいません';
Future<String> _fetchLesson(bool fail) async {
await Future<void>.delayed(const Duration(seconds: 1));
if (fail) throw Exception('練習用の失敗');
return '次の学習: Widget';
}
Future<void> _load(bool fail) async {
setState(() { _loading = true; _message = '読み込み中'; });
try {
final message = await _fetchLesson(fail);
if (!mounted) return;
setState(() { _message = message; });
} catch (error) {
if (!mounted) return;
setState(() { _message = '読み込みに失敗しました。再試行できます'; });
} finally {
if (mounted) setState(() { _loading = false; });
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('非同期の練習')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(_message),
if (_loading) const LinearProgressIndicator(),
ElevatedButton(
onPressed: _loading ? null : () { _load(false); },
child: const Text('成功を試す'),
),
ElevatedButton(
onPressed: _loading ? null : () { _load(true); },
child: const Text('失敗を試す'),
),
],
),
),
);
}
}初期表示: まだ読み込んでいません
「成功を試す」 → 読み込み中 → 約1秒後に次の学習: Widget
「失敗を試す」 → 読み込み中 → 約1秒後に失敗の案内
待機中は両方のボタンが無効になり、完了後に再び押せるawait後のmounted確認
awaitの間に画面から離れると、Stateがすでに破棄されている可能性があります。mounted を確認してからsetStateすると、破棄済みの画面を更新するエラーを避けられます。
setStateのコールバックは同期処理にします。中へasyncを付けず、外側の_loadでawaitし、更新する短い処理だけをsetStateで囲みます。finallyで_loadingを戻すことで、失敗した後も再試行できます。
受け取ったJSONを検証
実際のAPIへ進む前にJSON文字列の扱いを練習します。dart:convertのjsonDecodeは動的な値を返すため、必要な形かを検証します。これはbasics.dart用の独立した例です。
import 'dart:convert';
String parseTitle(String source) {
final Object? data = jsonDecode(source);
if (data is! Map<String, dynamic>) {
throw const FormatException('オブジェクトが必要です');
}
final Object? title = data['title'];
if (title is! String || title.isEmpty) {
throw const FormatException('titleが不正です');
}
return title;
}
void main() {
for (final source in ['{"title":"Widget"}', '{"title":10}']) {
try {
print(parseTitle(source));
} on FormatException {
print('データの形式が不正です');
}
}
}Widget
データの形式が不正ですis! は指定した型でないかを調べます。APIへ接続するときは、この検証に加えてHTTPの状態コード、通信失敗、タイムアウトも確認する必要があります。
練習:待ち時間と再試行
完成例の待ち時間を2秒へ変え、失敗した後に成功を試せるかを確認します。ボタンの無効化がなければ何が起こるかも考えてみましょう。
解答例と確認結果
await Future<void>.delayed(const Duration(seconds: 2));失敗を試す → 約2秒後に失敗の案内
その後に成功を試す → 約2秒後に次の学習: Widget
待機中の連打では新しい読み込みを開始しない同時に複数の読み込みを始めると、先に開始した古い結果が後から届くことがあります。この例は待機中のボタンを無効にして同時実行を防いでいます。より複雑な画面では処理のキャンセルや結果の識別も設計します。
公式ドキュメントで確認する:Dartの非同期処理 / Flutterの状態管理の基礎
