getx를 사용하다가 프로젝트의 규모가 점점 더 커질 것 같아서,
riverpod를 공부하기로 마음먹었다.
getx를 사용했을 때, 코드가 간결해져서 생산성이 높고,
global context로 어디에서나 객체 접근이 가능하지만
이것이 나중에는 단점이 될 수도 있다는 생각에 flutter에서 공식으로 밀고있는 provider와
그것의 개선안인 riverpod를 사용할 예정이다..
일단 본격적으로 riverpod에 대해 알아보자..
Riverpod가 생겨난 배경
▶ provider의 단점
- provider를 사용하다 보면 결국에는 UI 코드와 섞이게 되는 문제를 볼 수 있다.
main.dart
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Provider(
create: (context) => MyFirstClass(),
child: ProxyProvider<MyFirstClass, MySecondClass>(
update: (context, firstClass, previous) => MySecondClass(firstClass),
child: MyVisibleWidget(),
),
);
}
}
위의 코드를 보면 알 수 있다..
Provider로 위젯을 감싼 뒤 그 안에서 변화를 체크해야 하니 결국에는 UI와 복잡하게
얽혀있는 모습이다..
- 타입에 의존하는 provider
main.dart
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Provider(
create: (context) => 'A String far away.',
child: Provider(
create: (context) => 'A String that is close.',
builder: (context, child) {
return Text(Provider.of<String>(context));
},
),
);
}
}
String 타입의 객체를 동일하게 제공하면 제일 가까운 놈에만 접근 가능하여
결국 A String far away의 값을 얻지 못한다..
그 외에도 여러번 접근하려면 그만큼의 선언이 필요하여 불편하고 제한적이다..
MultiProvider(
providers: [
Provider<Something>(create: (_) => Something()),
Provider<SomethingElse>(create: (_) => SomethingElse()),
Provider<AnotherThing>(create: (_) => AnotherThing()),
],
child: Container(),
)
해당과 같이 main에서 사용할 provider들에 대해 명시해 주어야 한다. 결국엔 파일을 왔다 갔다 해야 할 것이다.
이러한 단점들을 보완한 것이 riverpod 이다.
▶ Riverpod의 장점
- provider를 일단 한번 선언하면 어디에서든 global 접근이 가능하다
final greetingProvider = Provider((ref) => 'Hello Riverpod!');
하지만 provider 자체는 flutter 위젯 트리에 종속된다. 따라서
전체 위젯트리를 ProviderScope으로 감쌀 필요가 있다.
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
- ConsumerWidget의 사용
이제 greetingProvider의 String 문자를 Text 위젯에 띄우는 방법에 대해 알아보자
1. superclass를 ConsumerWidget으로 변경
class MyApp extends ConsumerWidget{
...
2. 위젯의 build 메소드에 ScopedReader function 추가
Widget build(BuildContext context, ScopedReader watch){
...
3. 위젯의 상태를 체크하여 rebuild 시켜주는 watch를 통해 provider 전달
final greeting = watch(greetingProvider);
전체 코드
class MyApp extends ConsumerWidget {
@override
Widget build(BuildContext context, ScopedReader watch) {
final greeting = watch(greetingProvider);
return MaterialApp(
title: 'Riverpod Tutorial',
home: Scaffold(
appBar: AppBar(
title: Text('Riverpod Tutorial'),
),
body: Center(
child: Text(greeting),
),
),
);
}
}
오직 변화하는 Text만 감지를 해서 rebuild 해주고 싶을 때는 Consumer widget을 사용하면 된다.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
...
body: Center(
child: Consumer(
builder: (context, watch, child) {
final greeting = watch(greetingProvider);
return Text(greeting);
},
...
}
}
- AsyncValue의 사용으로 Future 처리를 더 쉽게 만들어준다.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
...
child: Consumer(
builder: (context, watch, child) {
final responseAsyncValue = watch(responseProvider);
return responseAsyncValue.map(
data: (_) => Text(_.value),
loading: (_) => CircularProgressIndicator(),
error: (_) => Text(
_.error.toString(),
style: TextStyle(color: Colors.red),
),
);
...
}
map을 통해 data가 존재할 때, 데이터를 불러오는 중인 loading 상태일 때, 데이터를 불러올 수 없는 error 상태일 때
각각 상태 처리를 한번에 가능하다.
- family를 통한 arguments 전달
final greetingFamilyProvider = Provider.family<String, String>(
(_, name) {
return "Welcome, $name!";
},
);
family를 통해 String 인자를 넘겨주는 provider를 선언한다
sendArguments(WidgetRef ref) {
ref.read(
greetingFamilyProvider('yerim')
);
}
sendArguments 함수에서 greetingFamilyProvider에 name 인자값을 넘겨주면 된다.
- autoDispose의 사용으로 자동으로 상태를 dispose 해줌
fake request가 있을 때, 직접 dispose 해주지 않는 이상 계속 존재하였다.
하지만 autoDispose는 provider가 사용되지 않을 때, 알아서 dispose 해주는 역할을 한다.
final responseProvider =
FutureProvider.autoDispose.family<String, String>((ref, url) async {
final httpClient = ref.read(fakeHttpClientProvider);
return httpClient.get(url);
});
정리해보자면,
Riverpod는
1. provider와 달리 compile time에 오류를 캐치할 수 있으며 (provider는 runtime에 오류가 난다..)
2. 동일한 유형의 여러 종속성을 주입할 수 있으며,
3. No BuildContext 즉, BuildContext가 더 이상 필요하지 않는다
간단한 튜토리얼에 대해 알아보았다..
다음엔 더 깊게 들어가 보도록 하자
'Flutter' 카테고리의 다른 글
[Flutter/플러터] listview 안에서 scroll 하기 (0) | 2022.10.14 |
---|---|
[Flutter/플러터] Riverpod와 StateNotifier에 대해 (0) | 2022.09.28 |
[Flutter/플러터] Riverpod를 통해 알아보는 플러터 아키텍처 - (2) Domain Model (0) | 2022.09.27 |
[Flutter/플러터] Riverpod를 통해 알아보는 플러터 아키텍처 - (1) Repository pattern (0) | 2022.09.27 |
[Flutter/플러터] Freezed 플러그인 사용하기 - (1) (0) | 2022.09.27 |