1. 상태관리란?
App state는 앱 전체에 영향을 주는 데이터를 말한다.
앱 상에서 데이터가 즉각적으로 rebuild되어 반영되어지고 관리되어져야하는 부분이 있다.
이런 데이터를 관리하는것을 앱 상태관리라고 말한다.
예를 들어,
- 로그인 정보
- 유저의 설정값
- 사용자의 알림
- 새로운 정보의 게시글 등...
- 유저간의 메세지 등...
이러한 App state를 관리하는 방법은 여러가지가 있다.
기본적으로 Stateful위젯속에서 setState메소드를 활용하여,
데이터가 변경되어 반영되야하는 부분마다 setState 메소드를 사용해주면 앱 상태가 관리되며, Rebuild된다.
하지만, 이런 Flutter 위젯을 통한 기본적인 방식으로는 복잡하고 구조적인 인터렉티브한 앱을 구현하는데 한계가 있어,
플러터를 제작한 구글에서도 특별히 상태관리에 사용을 권고하는 Plugin이 있었다.
바로 Provider 이다.
2. Provider , Riverpod
프로바이더는 앱의 전반적인 상태 관리에 사용되는 플러그인이다.
프로바이더는 하나의 상태조각의 압축(encapsulates)된 객체이자
데이터 상태의 변화를 감시하는 역할을 가지고 있다.
프로바이더를 사용하는 이유
- 코드상 다양한 위치에서 상태를 쉽게 접근할 수 있습니다. 프로바이더는 Singletons, Service Locators, Dependency Injection 또는 InheritedWidgets 과 같은 디자인 패턴들을 완벽하게 대체할 수 있습니다.
- 다른 프로바이더 상태와 간편하게 결합하여 사용할 수 있습니다.
- 퍼포먼스 최적화가 가능합니다. 위젯을 다시 빌드하는것을 필터링 하거나 비용이 높은 상태 계산을 캐싱하거나 프로바이더는 상태 변경의 영향을 받는 항목만 다시 계산합니다.
- 애플리케이션의 태스트 용이성이 높아집니다. 프로바이더와 함께라면, 복잡한 setUp/tearDown 단계가 불필요 합니다. 게다가 어떠한 프로바이더이든지 테스트 중의 프로바이더 행위를 오버라이드 할 수 있습니다. 매우 특정한 행위(동작)을 테스트 하기 쉽습니다.
- 고급 기능들과 함께 손 쉬운 통합이 가능합니다. 예를들어 로깅(logging) 또는 pull-to-refresh가 있습니다.
https://riverpod.dev/ko/docs/concepts/providers/
이렇게 좋은 프로바이더라도, 단점이 존재합니다.
런타임시 발생하는 많은 에러, 타입에 제한사항이 많았는데.
Riverpod는 주로 Provider의 이런 다양한 결점을 해결하기 위해
Remi Rousselet(Provider의 창시자)가 (근본적인 문제를 해결하기 위해서 새롭게) 다시 만들었습니다.
Riverpod는 Provider보다 사용하기 쉬우며 상태 관리를 위한 빠르고 가벼운 패키지라고 소개합니다.
Riverpod으로 개선된 사항들
- Riverpod은 컴파일 타임 동안 안전하다.
- Flutter SDK에 직접적으로 의존하지 않는다.
- Riverpod는 위젯 트리에 직접적으로 의존하지 않습니다.
- Provider는 전역적으로 선언되며 응용 프로그램의 모든 위치에서 사용할 수 있습니다.
- Riverpod는 위젯을 통해 제공자에 액세스할 수 있도록 하며 ScopedReader, 빌드 메소드에 전달되고 최종적으로 ConsumerWidget클래스 를 통해 소비됩니다.
- 상태 또는 UI Rebuild를 필요할 때만 한다.
- Riverpod은 loading/error 케이스를 깔끔하게 다룰 수 있다.
- devtool로 상태를 검사할 수 있다.
- . . .
3. Riverpod Example Code
riverpod에 간단한 사용 예제 코드이다.
처음 프로젝트를 만들면 나오는 Counter 앱을 Riverpod 패턴으로 구현한 것이다.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// A Counter example implemented with riverpod
void main() {
runApp(
/// 1. 최상위에 ProviderScope를 지정하여 project 전반에 프로바이더 선언/접근을 가능하게 한다
const ProviderScope(child: MyApp()),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(home: Home());
}
}
///2. globally하게 Providers 선언, how to create a state를 구체화, ref(int)에 초기값 지정
final counterProvider = StateProvider((ref) => 0);
/// 3. ConsumerWidget 을 extends
class Home extends ConsumerWidget {
/// 4. build 메소드에 WidgetRef ref 인자 추가
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(title: const Text('Counter example')),
body: Center(
/// 5. Consumer 위젯 안에서 providers reading 가능하도록 설정.
child: Consumer(builder: (context, ref, _) {
/// watch 메소드를 통해 값을 연결해서 변경여부를 주시하게 함 (변경 발생시 Rebuild)
final count = ref.watch(counterProvider.state).state;
return Text('$count');
}),
),
floatingActionButton: FloatingActionButton(
/// + Read 메소드를 통해 값을 셋팅
onPressed: () => ref.read(counterProvider.state).state++,
child: const Icon(Icons.add),
),
);
}
}
차근차근 하나씩 설정 방법을 살펴보자.
1) 앱의 최상단에 ProviderScope 를 지정
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() => runApp(ProviderScope(child: RiverPodApp()));
기존에 provider와 달리 앱에 최상단에서 ProviderScope를 한번만 감싸줍니다.
생성된 모든 페이지와 위젯이 Providers 범위로 지정되어,
앱 어디서든지 Providers를 선언 / 접근 /사용할 수 있도록 합니다.
2) Provider 선언
///2. globally하게 Providers 선언, how to create a state를 구체화, ref(int)에 초기값 지정
final counterProvider = StateProvider((ref) => 0);
다양한 프로바이더 중 StateProvider로 ref 에 초기값 0을 넣어줍니다.
3,4) 클래스 extends ConsumerWidget , Build내 WidgetRef red 추가
/// 3. ConsumerWidget 을 extends
class Home extends ConsumerWidget {
/// 4. build 메소드에 WidgetRef ref 인자 추가
@override
Widget build(BuildContext context, WidgetRef ref) {
해당 클래스가 소비위젯임을 명시하여 Ref에 변경에 Rebuild 되도록 지정합니다.
5) Consumer 위젯과 ref. watch
/// 5. Consumer 위젯 안에서 providers reading 가능하도록 설정.
child: Consumer(builder: (context, ref, _) {
/// watch 메소드를 통해 값을 연결해서 변경여부를 주시하게 함 (변경 발생시 Rebuild)
final count = ref.watch(counterProvider.state).state;
return Text('$count');
}),
변경을 주시하는 부분을 Consumer 위젯으로 감싸주고,
함수를 작성하여 재랜더링 되야하는 부분의 위젯을 반환하도록 작성합니다.
6) Provider 셋팅부분 ref. read
floatingActionButton: FloatingActionButton(
/// + Read 메소드를 통해 값을 셋팅
onPressed: () => ref.read(counterProvider.state).state++,
child: const Icon(Icons.add),
),
Read 메소드를 통해 데이터를 읽어 변경시킵니다.
현재 사용가능한 provider
provider 는 다른 객체 타입에 대해 몇 가지 다른 "provider"를 제공합니다.
모든 객체의 리스트는 여기에서 확인할 수 있습니다.
Provider | The most 기본적인 provider 형태. 어떤 값이던 간에 값을 노출시킵니다. |
ListenableProvider | Listenable 객체를 위한 특수한 provider. ListenableProvider는 listener가 호출될 때마다 오브젝트를 수신하고 오브젝트에 종속된 위젯을 재구성하도록 요청합니다. |
ChangeNotifierProvider | ChangeNotifier용 ListenableProvider 사양. 필요할 때 자동으로 ChangeNotifier.dispose를 호출합니다. |
ValueListenableProvider | ValueListenable을 수신하고, ValueListenable.value만을 노출합니다. |
StreamProvider | 스트림을 수신하고 최신 값을 표시합니다. |
FutureProvider | Future를 받고, 완성되었을 때 의존된 객체를 업데이트합니다. |
참조 사이트
https://github.com/rrousselGit/provider
https://blog.logrocket.com/provider-vs-riverpod-comparing-state-managers-in-flutter/
https://prod.velog.io/@leeeeeoy/Flutter-Riverpod-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0-1
'Study > Flutter' 카테고리의 다른 글
[Flutter] Appbar 상단바와 하단바 예제 (0) | 2022.05.24 |
---|---|
[Flutter] Chip, Badge 사용법 (0) | 2022.05.24 |
[Flutter] 기본 레이아웃 Widget (0) | 2022.05.16 |
[Flutter] Freezed Plugin (0) | 2022.05.16 |
[Flutter] 유용한 Plugin (0) | 2022.05.12 |