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
복사