LoginSignup
8
1

More than 1 year has passed since last update.

Riverpodでどこでも使えるLoading Dialog(っぽいもの)を作る

Last updated at Posted at 2022-12-21

アプリの開発をしていると、非同期の処理をしている間はローディングをしているようなViewを表示して、その他の入力を受け付けないようにしたいケースがあると思います。
androidのProgressDialogの非推奨の理由を見る限り、googleはそういったUXを排除したいようですが、欲しいものは欲しいのでRiverpodと組み合わせてFutureが完了するまで表示されるローディングViewを実装しました。

環境

Flutter : 3.3.10-stable
Riverpod : 2.1.1

ディレクトリ

lib
├── app.dart
├── main.dart <-- ProviderScopeとMyAppを読み込むだけ
└── loading
    ├── loading.dart
    ├── loading_container.dart
    ├── loading_notifier.dart
    └── loading_screen.dart

loadingの状態を管理するnotifierを作る

bool型のStateNotifierを作成
Futureを受け取り、Future実行前にstateをtrue, 実行後にstateをfalseにするメソッドを定義

loading_notifier.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

final loadingProvider = StateNotifierProvider<LoadingNotifier, bool>((ref) => LoadingNotifier());

class LoadingNotifier extends StateNotifier<bool> {
  LoadingNotifier() : super(false);

  Future<T> whileLoading<T>(Future<T> Function() future) {
    return Future.microtask(toLoading)
        .then((_) => future())
        .whenComplete(toComplete);
  }

  void toLoading() => state = true;

  void toComplete() => state = false;
}

loadingのViewを作る

loading.dart
import 'package:flutter/material.dart';

class Loading extends StatelessWidget {
  const Loading({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return DecoratedBox(
      // 背景透過のため
      decoration: const BoxDecoration(color: Color.fromRGBO(0, 0, 0, 0.4)),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          // View本体
          Container(
            width: 100,
            height: 100,
            decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.circular(8),
            ),
            child: const Center(
              child: CircularProgressIndicator(),
            ),
          ),
        ],
      ),
    );
  }
}

Loading ViewをラップするContainerも作る
ここでloadingの状態を受け取り、状態によってLoading Viewの表示を出し分ける

loading_container.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:progress_dialog_sample/progress/loading_notifier.dart';

import 'loading.dart';

class LoadingContainer extends ConsumerWidget {
  const LoadingContainer({
    Key? key,
    required this.child,
  }) : super(key: key);

  final Widget? child;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final loading = ref.watch(loadingProvider);

    return Stack(
      fit: StackFit.expand,
      children: [
        child ?? const SizedBox(),
        loading ? const Loading() : const SizedBox(),
      ],
    );
  }
}

builderに渡すためのTransitionBuilderを定義

作っておくと他のbuilderに渡すものと共存させやすいので

loading_screen.dart
import 'package:flutter/material.dart';

import 'loading_container.dart';

mixin LoadingScreen {
  static TransitionBuilder init({
    TransitionBuilder? builder,
  }) {
    return (BuildContext context, Widget? child) {
      if (builder != null) {
        return builder(context, LoadingContainer(child: child));
      } else {
        return LoadingContainer(child: child);
      }
    };
  }
}

MaterialAppのbuilderに↑を渡す

builderに渡すことで、どの画面でもloading_notifierの状態が変わればローディングのViewを表示させることができる

app.dart
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      builder: LoadingScreen.init(),
      home: const LoadingSample(title: 'loading progress sample'),
    );
  }
}

動かす

ボタンを押すと3秒delayするFutureが動く
Futureが止まるまでLoadingのViewが表示される

8
1
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
8
1