1.背景
initState()中で、(うっかり)非同期処理を行っていた時の障害です。
2.障害内容
2-1.エラーメッセージ:
Dart
Launching lib\main.dart on Windows in debug mode...
√ Built build\windows\x64\runner\Debug\reorderblelistview_11_02_02.exe
Connecting to VM Service at ws://127.0.0.1:65194/UkCP5XaaydQ=/ws
════════ Exception caught by widgets library ═══════════════════════════════════
The following assertion was thrown building InputDecorator(decoration: InputDecoration(labelText: "カテゴリ選択", isDense: true, contentPadding: EdgeInsets.all(10.0), border: OutlineInputBorder()), isFocused: false, isEmpty: false, dependencies: [Directionality, _InheritedTheme, _LocalizationsScope-[GlobalKey#3805d]], state: _InputDecoratorState#38001(tickers: tracking 2 tickers)):
DropdownMenuState.initState() returned a Future.
State.initState() must be a void method without an `async` keyword.
Rather than awaiting on asynchronous work directly inside of initState, call a separate method to do this work without awaiting it.
The relevant error-causing widget was:
InputDecorator InputDecorator:file:///C:/app/flutter/09_new/09/reorderblelistview_11_02/lib/screens/task_add_page.dart:79:23
When the exception was thrown, this was the stack:
#0 StatefulElement._firstBuild.<anonymous closure> (package:flutter/src/widgets/framework.dart:5621:9)
#1 StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:5631:6)
#2 ComponentElement.mount (package:flutter/src/widgets/framework.dart:5463:5)
═══════════════════════════════════════════════════════════════════════════════
flutter: (loadCate()中、setState()中、正常終了) loadCate_ret: 0
flutter: loadCate()中、setState()中、categories: [初期カテゴリ]
flutter: loadCate()中、setState()中、selectedCategory: 初期カテゴリ
flutter: initState()中、setState()中、categories: [初期カテゴリ]
flutter: initState()中、setState()中、selectedCategory: 初期カテゴリ
2-2.エラー発生場所:
(エラー発生場所)initState(){}中で非同期処理をしている(8,11行目):
class DropdownMenuState extends State<DropdownMenu> {
List<Map<String, dynamic>> cates = [];
List<String> categories = [];
String? selectedCategory;
ManageDbProcess dbProcess = ManageDbProcess();
@override
Future<void> initState() async{
super.initState();
int ret = await loadCate();
if (ret != -1) {
selectedCategory = categories.first;
if (kDebugMode) {
print('initState()中、setState()中、categories: $categories');
print('initState()中、setState()中、selectedCategory: $selectedCategory');
}
} else {
if (kDebugMode) {
print('initState()中、setState()中、取得失敗');
}
}
}
どうやら、initState() メソッド内で非同期処理を実行しようとしたために発生した様です。
Flutterでは、initState() メソッドは同期的でなければならず、async キーワードを使用できないのでした。
これは、initState() がウィジェットのライフサイクルの初期段階で呼び出され、ウィジェットの初期化を行うために設計されているためで、つまり非同期処理は、ウィジェットが完全に初期化される前に完了する保証がなく、ウィジェットの構築に影響を与えてしまう可能性があるから、(ここで)使ってはいけませんよ、ということらしい。
3.修正内容
(修正後)iintState(){}内の非同期処理を分離した:
class DropdownMenuState extends State<DropdownMenu> {
//List<String> categories = ['One', 'Two', 'Three', 'Four'];
List<Map<String, dynamic>> cates = [];
List<String> categories = [];
String? selectedCategory;
ManageDbProcess dbProcess = ManageDbProcess();
@override
void initState() {
super.initState();
// 修正↓)loadCate().then((_) {
// 修正↓)int ret = await loadCate();
late Future loadCateRet;
loadCateRet = loadCate();
if (kDebugMode) {
print('initState()中、loadCate()の結果: loadCateRet: $loadCateRet');
}
}
// 修正↓)Future<int> loadCate() async{
Future<void> loadCate() async{
// ↓修正)移植準備
// ↓修正)dbProcess.manageFetchAllCates();
//List<Map<String, dynamic>>> retCate = [];
int ret = await dbProcess.manageFetchAllCates();
int loadCateRet = 0;
if (ret != -1) {
setState(() {
cates = dbProcess.cates;
// catesテーブルデータが1件以上あった場合
if (cates.isNotEmpty) {
// categoriesリストにdescriptionを追加
for (var c in cates) {
categories.add(c['description']);
}
loadCateRet = 1;
} else { // catesテーブルデータが0件だった場合
// categoriesリストに”初期カテゴリ”を追加
categories.add("初期カテゴリ");
loadCateRet = 0;
}
selectedCategory = categories.isNotEmpty ? categories.first : null;
});
if (kDebugMode) {
print('(loadCate()中、setState()中、正常終了) loadCate_ret: $loadCateRet');
print('loadCate()中、setState()中、categories: $categories');
print('loadCate()中、setState()中、selectedCategory: $selectedCategory');
}
//return loadCateRet; // 正常終了
} else {
if (kDebugMode) {
print('loadCate()中、setState()中、取得失敗');
}
//return -1; // 異常終了
}
}
上記でエラーは解消しました。
(今回は、FutureBuilder() は使用しなかったのですが、UI構築には影響していなかった為、問題ありませんでした)