1
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

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

本記事は 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'),
),

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
1
Help us understand the problem. What are the problem?