LoginSignup
5
2

More than 3 years have passed since last update.

Flutterでダイアログを開く際に非同期処理をする

Posted at

本記事は CBcloud Advent Calendar 2020の20日目の記事です.

自分はCBcloudに,toCサービスであるPickGo 買い物のアプリを主に担当するモバイルアプリエンジニアとして参画しています.
PickGo 買い物は,プロのドライバーさんに買い物を代行してもらえ,欲しい物が当日すぐに届くアプリです.
現在送料無料キャンペーンをやっているので良かったらこの機会に是非試してみてください!

この記事では,自分が業務中に編み出したテクニックの一つを紹介します.

ダイアログを出す時に非同期処理をしたい

ユーザーの操作からダイアログを出すとき,その内容をAPIから取得したいシチュエーションを考えます.

↓これ
async.gif

何かしら非同期で通信している場合は,ロード中であることをこのようにインジケーターを出すことで親切になります.これは別に元のページでやっても良いのですが,どうせダイアログを出すのであればダイアログ上でやったほうがなんか見た目も良い気がします.

これを実現するために,まずダイアログのWidget上で普通のページ用Widgetと同じようにStatefulWidgetでいうinitStateChangeNotifierでいうコンストラクタ)で非同期処理をすることを考えました.

…しかしただやるだけではうまく行かず.ちょっと前の話なので記憶が曖昧ですが,確かProviderでうまくChangeNotifierを呼び出せなかったことが原因だったと思います.おとなしくStatefukWidgetを使ってたらできていたかもしれませんが,StatefulWidgetをなるべく使いたくない気持ちが勝って更に探っていました.

実装方法

色々考えたところ,以下の方法で落ち着きました.

  1. showDialogのラッパー関数を用意する
  2. 内部でProvider.valueを使い,ViewModel的な役割を果たすControllerを利用する
  3. ラッパーの引数に実際に行う非同期処理の内容と,その結果を含んだWidgetBuilder的なものを定義しておく

実装はこのようになります.


typedef ResultBuilder<R> = Widget Function(BuildContext, R);
typedef PreProcess<R> = Future<R> Function();

Future<T> showAsyncDialog<T, R>({
  BuildContext context,
  ResultBuilder<R> resultDialogBuilder,
  PreProcess<R> process,
}) async {
  final controller = _AsyncDialogController(process: process);

  final dialogResult = await showDialog(
      context: context,
      builder: (context) {
        final dialog = Builder(
          builder: (context) {
            final isLoading = context.select(
                (_AsyncDialogController<R> controller) => controller.isLoading);
            final result = context.select(
                (_AsyncDialogController<R> controller) => controller.result);
            return isLoading
                ? const PlaceholderDialog()
                : resultDialogBuilder(context, result);
          },
        );

        return ChangeNotifierProvider.value(
          value: controller,
          child: dialog,
        );
      });

  return dialogResult;
}

class _AsyncDialogController<T> extends ChangeNotifier {
  _AsyncDialogController({this.process}) {
    Future(() async {
      result = await process();
      isLoading = false;
      notifyListeners();
    });
  }

  bool isLoading = true;
  final Future<T> Function() process;
  T result;
}

ダイアログ中のProviderアクセスは https://stackoverflow.com/questions/57968453/how-to-access-provider-providers-in-dialogs-in-flutter を参考にしました.

PlaceholderDialogはロード中に出すダイアログで,結果のダイアログとは別物にしてあります.
ここの実装を工夫すれば処理完了した時にアニメーション付きで結果のダイアログがスムーズに出せそうな気がします.

利用側はこんな感じです.

TextButton(
  onPressed: () => showAsyncDialog<void, String>(
    context: context,
    resultDialogBuilder: (context, result) => AlertDialog(
      content: Text(result),
    ),
    process: () async {
      await Future.delayed(const Duration(seconds: 2));
      return 'hoge';
    },
  ),
  child: Text('Tap me'),
),

実装してみればなんてことのないものでしたが,個人的に作っておくと結構便利だなと思いました.

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2