Flutter-GetxControllerでのフェッチ処理

GetxController派生でのデータベース等からのフェッチ処理について、FutureBuilderと関連させる必要があったのでちょっと書いてみた。

初めに

GetxController派生をViewModelとして、View側で表示させる情報をViewModel側で構築するためにはどうしたらいいのかを考えていた。

これに関して調べていたら、以下のような記述がヘルプにあったのでonInit内でデータを読み込ませればいいのかと当初は考えていた。

https://github.com/jonataslaw/getx/blob/master/documentation/ja_JP/state_management.md#statefulwidgets%E3%81%AF%E3%82%82%E3%81%86%E3%81%84%E3%82%89%E3%81%AA%E3%81%84
@override
void onInit() {
  fetchApi();
  super.onInit();
}

またちょっと違うところに、onInitにasyncを付けて非同期処理ができるような感じだったので、この部分でデータを読み込み表示用データを構築するんだと思った。

今回データを持ってくるもとはsqlite。データをの取得としてどうしても非同期にならざるを得なかった。

実際その方法で実装して、ちょっとテストをして見たところ、思った通りに動かなかった。
(よく考えればわかるんだけど)

調べてみたら、GetX側でonInitの呼び出し側は非同期呼び出しじゃないので、onInitにasyncを付けたら定義したonInitは呼びっぱなし。
そのため、データのロードが終わる前にウィジェットの作成が進んでいってしまい、表示用のデータをViewModel側から取り出そうとしたところで、そのようなデータがないという結果になってしまった。

対応方法

結果としては当たり前の方法なのだけど、表示データの構築処理はFutureな関数にして、View側はFutureBuilderで状態監視をするという、ごくありふれた結果になった。

ただ、表示データの構築(データベースからの読み込み)はViewが表示されたときに1度行えばいいので、それらあたりをまとめた処理をクラス化してみてそれを使用するようにした。

それが以下のクラス。

import 'package:get/get.dart';

/// Loaderを起動するためのインターフェースクラス
abstract class ILoadbleDB extends GetxController {
  Future<void> loadDB();
}

/// DBをロードさせるための仕組み。
/// withしてFutureBuilderでloaderを指定する。
mixin Loader on ILoadbleDB {
  /// [loadDB]が呼び出されていない場合true。
  bool _isNotLoadDb = true;

  /// 再読み込みの指示
  void reset() => _isNotLoadDb = true;

  /// データベースのローダー
  Future<void> loader() async {
    if (_isNotLoadDb) {
      _isNotLoadDb = false;
      await loadDB();
    }
  }
}

使用する側は以下の様に宣言し、FutureBuilderのfutureにはlorder()メソッドを渡すようにする。

class Hoge extends ILoadbleDB with Loader

これで、loadDBというデータベース読み込みと表示データ構築処理は1度だけの呼び出しという感じになる。

FutureBuilder<void>

FutureBuilder<void>だと、builderでのFuture状態判断としてhasDataは使えない。
当然void型なので。

よくあるサンプル類だと、hasData、hasErrorでその表示するウィジェットの状態を決めているのが多いのだけど、その流れでいうとFutureBuilder<void>だと正しく実装できない。

実際はconnectionStateを使いFutureの状態を監視して、さらにhasDataやhasErrorを見て表示するウィジェットを決めるというのが正しいやり方になる。

FutureBuilder class - widgets library - Dart API
API docs for the FutureBuilder class from the widgets library, for the Dart programming language.

上のDatPad内のサンプルがそうだったので、それが定石だと思ってしまった。

FutureBuilder<void>でFutureが正しく実行できたかどうかの判断は、結果以下の様になる。

snapshot.connectionState == ConnectionState.done && !snapshot.hasError

else側は、snapshot.hasErrorであればエラーウィジェットの表示、それ以外は読み込み中ウィジェットの表示としておけばいい感じになると思う。

一応、Future<void>側でエラーがあった場合は、return Future.error()でリターンする。
エラーの内容等があるのであれば、その関数内で情報を返し、ウィジェット側に表示するといった感じになるのだろう。

GetBuilder-FutureBuilderの組み合わせ

もう一点動作的に戸惑ったのが、GetBuilder->FutureBuilderという階層構造にして、FutureBuilderのFuture内でGetxControllerのupdateを呼び出した、というシチュエーション。

構造的には上のような感じのもので、Futureの中でupdateすれば、GetBuilderの下にあるTextButtonの表示を変えらえるかなと思ったのだけど、うまくいかなかった。

updateは呼び出してるんだけどGetBuilder以下のウィジェットが再作成されなかった。

原因は多分だけど、Future実行中はGetBuilder以下のウィジェット構築中で、その中でupdateをしてもGetBuilderの再構築フラグが立たなかった(もしくはどこかでクリアされていた)のだと思う。

結果としてFutureBuilderをもっと上位側、具体的にはGetBuilderの直下に配置し、Future完了した正しい表示にするといったものにしたところ解決した。

まあ、これも当たり前と言えば当たり前なんだろうな。

コメント

タイトルとURLをコピーしました