Provider 를 활용해 state 를 관리하는 Flutter app 에서 loading indicator를 어떻게 다루는지 정리합니다.
각 Provider들의 loading 상태의 관리
Provider 들은 각각 network api 등 비동기적 작업을 수행할 수 있고 이 시간동안 사용자에게 현재 작업이 진행중임을 알리는 커뮤니케이션을 해야합니다. 주로 loading indicator 를 활용합니다.
BaseChangeNotifier
import 'package:flutter/cupertino.dart';
class BaseChangeNotifier extends ChangeNotifier {
  bool _disposed = false;
  bool _isLoading = false;
  bool isLoading() {
    return _isLoading;
  }
  void setLoading(bool isLoading) {
    if (_isLoading != isLoading) {
      _isLoading = isLoading;
      notifyListeners();
    }
  }
  
  void dispose() {
    _disposed = true;
    super.dispose();
  }
  
  void notifyListeners() {
    if (_disposed) {
      return;
    }
    super.notifyListeners();
  }
}
Dart
복사
Provider loading 상태의 조합
LoadingProvider
class LoadingProvider extends BaseChangeNotifier {}
Dart
복사
LoadingProvider 는 다른 Provider 들의 상태를 조합하기 위한 단순 Wrapper 정도로 사용합니다. BaseChangeNotifier 를 상속받고 그 이상의 추가 구현 내용은 없습니다.
RootPage (Full code)
class RootPage extends StatefulWidget {
  
  RootPageState createState() => RootPageState();
}
class RootPageState extends State<RootPage> {
  late AuthProvider authProvider;
  late UserProvider userProvider;
  
  Widget build(BuildContext context) {
    authProvider = Provider.of<AuthProvider>(context);
    userProvider = Provider.of<UserProvider>(context);
    if (authProvider.getUser() != null &&
        userProvider.getServiceUser() != null) {
      return MultiProvider(
          providers: [
            ChangeNotifierProvider<PlantRegistrationProvider>(
              create: (BuildContext context) => PlantRegistrationProvider(),
            ),
            ChangeNotifierProxyProvider<PlantRegistrationProvider,
                MyPlantsProvider>(
                create: (BuildContext context) => MyPlantsProvider(),
                update: (BuildContext context,
                    PlantRegistrationProvider plantRegistrationProvider,
                    MyPlantsProvider? myPlantsProvider) =>
                    myPlantsProvider!.update()),
            ChangeNotifierProvider<WriteDiaryProvider>(
              create: (BuildContext context) => WriteDiaryProvider(),
            ),
            ChangeNotifierProxyProvider2<MyPlantsProvider,
                WriteDiaryProvider,
                DiaryProvider>(
                create: (BuildContext context) => DiaryProvider(),
                update: (BuildContext context,
                    MyPlantsProvider myPlantsProvider,
                    WriteDiaryProvider writeDiaryProvider,
                    DiaryProvider? diaryProvider) =>
                    diaryProvider!.update()),
👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇
            ChangeNotifierProxyProvider4<MyPlantsProvider, WriteDiaryProvider, DiaryProvider, PlantRegistrationProvider ,LoadingProvider>(
                create: (BuildContext context) => LoadingProvider(),
                update: (BuildContext context,
                    MyPlantsProvider myPlantsProvider,
                    WriteDiaryProvider writeDiaryProvider,
                    DiaryProvider diaryProvider,
                    PlantRegistrationProvider plantRegistrationProvider,
                    LoadingProvider? loadingProvider) {
                  var isLoading = myPlantsProvider.isLoading() || writeDiaryProvider.isLoading() || plantRegistrationProvider.isLoading() || diaryProvider.isLoading();
                loadingProvider!.setLoading(isLoading);
                return loadingProvider!;
            }
            )
👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆
          ],
          child: Consumer<LoadingProvider>(builder: (_, loadingProvider, __) {
            return Stack(
              children: [
                BottomTabPage(),
                Container(
                    child: loadingProvider.isLoading() ? LoadingIndicator() : Container()
                )
              ],
            );
          })
      );
    } else {
      return LoginPage();
    }
  }
}
Dart
복사
RootPage (Loading 상태를 조합하는 부분)
ChangeNotifierProxyProvider4<MyPlantsProvider, WriteDiaryProvider, DiaryProvider, PlantRegistrationProvider ,LoadingProvider>(
                create: (BuildContext context) => LoadingProvider(),
                update: (BuildContext context,
                    MyPlantsProvider myPlantsProvider,
                    WriteDiaryProvider writeDiaryProvider,
                    DiaryProvider diaryProvider,
                    PlantRegistrationProvider plantRegistrationProvider,
                    LoadingProvider? loadingProvider) {
                  var isLoading = myPlantsProvider.isLoading() || writeDiaryProvider.isLoading() || plantRegistrationProvider.isLoading() || diaryProvider.isLoading();
                loadingProvider!.setLoading(isLoading);
                return loadingProvider!;
            }
            )
Dart
복사
ChangeNotifierProxyProvider4 를 활용해 loading 상태를 갖는 MyPlantsProvider, WriteDiaryProvider, DiaryProvider, PlantRegistrationProvider 4개의 Provider 를 묶습니다.
  4개의 Provider state 중 1개 이상이 loading 상태인 경우 loadingProvider가 loading 상태를 갖도록 합니다.
RootPage (Loading 상태를 사용하는 부분)
Consumer<LoadingProvider>(builder: (_, loadingProvider, __) {
            return Stack(
              children: [
                BottomTabPage(),
                Container(
                    child: loadingProvider.isLoading() ? LoadingIndicator() : Container()
                )
              ],
            );
          })
Dart
복사
Stack을 활용해 기존 화면위에 LoadingIndicator를 올립니다.
LoadingIndicator 를 노출하는 조건은 loadingProvider의 isLoading 상태입니다.
FYI) LoadingIndicator
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
class LoadingIndicator extends StatelessWidget  {
  
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        ModalBarrier(),
        Center(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.center,
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                SpinKitFadingCircle(
                  itemBuilder: (BuildContext context, int index) {
                    return DecoratedBox(
                      decoration: BoxDecoration(
                        color: index.isEven ? Colors.green : Colors.orange,
                      ),
                    );
                  },
                )
              ],
            )
        ),
      ],
    );
  }
}
Dart
복사

