Search
🌦️

Loading Indicator with Provider pattern

생성일
2022/04/24 11:31
태그
Flutter
속성
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
복사

GIF