Flutter,アプリ起動時に処理実行(ウィジェット構築後)

0,目次

  1. はじめに
  2. 打開策
  3. 実装
  4. 参考文献

1,はじめに

Flutterでアプリを作成しているのですが、

SQLiteに登録したデータの読み込み完了よりも先に、

ウィジェットが構築されていて、

画面上へのDB登録値の反映が、

何らかのイベントが発生するまで行われない事象に見舞われました。

本稿では、技術的に適切かは不明ですが、今後のために私の打開策をメモして残します。

(なお、私の場合、処理の関係上、initStateでは対応できませんでした。ですが、通常はinitState内に処理を記載すれば、アプリ起動時(当該ステートの作成時)の処理は実装可能です)

裏でマジでいろいろ試したが、ダメでFlutter嫌いになりかけた

2,打開策

ウィジェットの構築完了後に非同期処理でsetStateを実行しました。

3,実装

(1)homepage.dart

class PageWidgetOfHomeState extends State<PageWidgetOfHome> {
    // 概念のお伝えのみのため、不要個所につきコードを省略しています。

    // https://stackoverflow.com/questions/51216448/is-there-any-callback-to-tell-me-when-build-function-is-done-in-flutter
    bool _executeOnce = false;
    Future<void> executeAfterBuild() async {
        if (_executeOnce) {
            return;
        }


        await loadDatabase();

        setState(() {
print("renew satrt");
            calcStockValues();
            _executeOnce = true;
print("renew end");
        });
    }

    @override
    Widget build(BuildContext context) {
        executeAfterBuild();
        return (_createHomePage(context));
    }
}

(2)common.dart

// DB
Database g_database = null;

Future<void> connectDatabase() async {
print("[DB CONNECT START]");
try {
    if (g_database != null) {
print("[DB CONNECTED]");
        return;
    }

    g_database = await openDatabase(
        join(await getDatabasesPath(), 'database.db'),
        version: 1,
        onCreate: (Database db, int version) async {
            // 取引ログテーブル(LOGDAT)が存在しない場合に、新規作成する
            //https://cha-shu00.hatenablog.com/entry/2017/10/11/091751
            String tSql = 
                "CREATE TABLE IF NOT EXISTS LOGDAT ("
                    "ID INTEGER PRIMARY KEY,"
                    "TYPE INTEGER,"
                    "PRICE INTEGER,"
                    "NUMBER INTEGER,"
                    "DATESTR TEXT,"
                    "POS_STK_AVE_PRICE INTEGER"
                ")";

print(tSql);
            await db.execute(tSql);
        }
    );

//    await t_db.close();
}
catch (e) {
    print(e);
}
print("[DB CONNECT START FIN]");
    return;
}


// データベースから取引ログを読み込み
// 取引履歴を構築
bool g_flagIsDbLoaded = false;

Future<void> loadDatabase() async {
    if (g_flagIsDbLoaded) {
        // 初期起動時に読み込みが完了しているならば、
        // 2度読み込みは行わない。
print("[LOAD] error db has already loaded.");
        return;
    }

    if (g_database == null) {
        await connectDatabase();
    }

    String tSql =
        "SELECT * FROM LOGDAT "
        "ORDER BY ID ASC";

    List<Map> result = await g_database.rawQuery(tSql);

    // 実行結果から取引履歴を構築
    for (Map item in result) {
        TradeInfo t = new TradeInfo.fillByDatabase(
            int.parse(item['TYPE'].toString()),
            item['PRICE'],
            item['NUMBER'],
            item['DATESTR']);

        // 売却ログの売却時の平均取得価格
        if (t.type == 2) {
            t.pricePossessionStockAve = item['POS_STK_AVE_PRICE'];
        }

        // 取引ログに追加
        tradeInfo.add(t);
print(t.toString());
    }

    g_flagIsDbLoaded = true;

    // DB読み込み後平均取得価格等の初期計算を実施
    calcStockValues();
}
catch (e) {
    print(e);
}

print("[DB LOAD END]");
    return;
}

(3)どういう処理なのか

Flutterのウィジェットツリーの構築がされるbuild内にて、

async付きのexecuteAfterBuildを呼び出します。

DBから値を読み込む処理(loadDatabase)は、

Flutterなどの仕様上、SQLクエリの実行結果が非同期で返されます。

そのため、ウィジェットツリーがDBから応答が返ってくる前に作成し終わってしまうことがあります。

(データバインディングなんだから自動的に再描画するかと思いきや、まったく再描画されないんですよね・・・わけわかめ)

(4)端的に言うと

buildメソッド内でDB読み込み処理をawait付きで同期実行し、

結果が戻り次第、setStateで更新をかけました。

(5)あれ?

普通に、initState内で、async付きの非同期実行メソッドを呼び出し、その中で読み込み処理をawaitし、その後setStateすればよかっただけじゃん・・・

かなしみ。。。

4,参考文献

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA