리버팟 2.0 간단한 공부 정리
최근 현업 프로잭트에서 riverpod을 사용하여 프로젝트를 진행하는 중이다.
또한 flutter discord 채널을 통해서 riverpod 관련 발표자료를 공유받아 최근 정리하였다.
들어가기 전에 간단한 사견
개인 공부로는 bloc을 사용해서 프로젝트를 진행하고 있는데 아무래도 상태관리를 지원하는 패키지인만큼 유사한 부분들이 많이 보인다.
state를 중심으로 state의 변화에 따라 UI의 동작을 결정한다는 측면에서는 유사한 측면을 보인다.
riverpod은 riverpod 하나로 서비스로케이터, 싱글톤, 의존성 주입 같은 요소들을 전부 할 수 있어서 사용하는 측면에서 편리하다는 느낌을 받는다. 추가적으로 riverpod내부에서 다른 riverpod을 결합할 수 있다는 특징이 로직을 설계하는 상황에서 편리한 부분이 많다.
다만, logic과 event의 분리를 명확하게 나누는 작업이 살짝 번거로울 수 있다. 또한 초심자일 때는 여러 provider를 결합하는 과정에서 lifecycle 관련해서 햇갈리는 부분이 생길 수 있다.
어디까지나 패키지의 적용범위가 넓고 자동화된 부분이 많다는 점에서 기인하는 문제점이 있긴하다. 의도하지 않는 rebuild가 일어나거나 lifecycle 관리로 인해 오류가 발생하는 등의... (이걸 단점이라고 해야 할지는 잘 모르겠지만) 그냥 쓰기는 쉽지만 잘 쓰려면 동작 원리를 조금 더 상세하게 공부할 필요가 있다.
bloc의 경우 상태관리라는 측면에서는 더 상세하게 관리할 수 있다는점이 좋은 것 같다. 하나의 bloc단위 내부에서 event / logic/ state의 분리가 더 명확하게 이루어지기에 특정한 상태의 관리라는 측면이 좋다.
단, rivierpod과는 달리 DI, 서비스 로케이터 등을 지원하기 위해서는 추가적인 패키지를 사용해야 한다. (주로 get_it, provider등...) 또한 bloc에서는 다른 bloc을 부르는 것이 어렵기도 하고 권장하지도 않는다. 즉, bloc의 결합을 통한 로직을 설계하는 부분이 상당히 번거롭다.
(물론 이 부분도 내가 bloc을 재대로 사용하지 못해서 발생하는 문제일 수 있으나...)
event/ logic/ state의 분리가 명확하다는 뜻은 아무리 작은 단위여도 이 부분들을 전부 다 구현해야 한다는 것을 의미한다. 즉, 코드의 절대량이 많아진다. 단, plugin을 통해 일정부분 자동화가 가능하기는 하며, cubit을 사용하면 단순하게 event와 logic을 한번에 처리 가능하지만 cubit도 bloc이기에 결합은 사실상 하지 않는것을 권장하며, 모든 logic을 cubit으로 할거면 그냥 riverpod을 사용하는 것이 나을 수 있다.
결론적으로 뭐가 더 좋다! 라고 말하기는 조금 애매하긴 하지만, 일반적인 의견으로는 상태의 변화와 관리가 복잡해지는 대규모 프로젝트에서는 bloc이 좋고 일반적으로는 riverpod이 더 좋다고들 하는 일반적이고 별 도움 안되는 결론에 도달하게 되는 것 같다...
두 패키지 모두 공식문서와 공식 예제가 잘 나와있으니 한번 직접 간단한 예제앱을 만들어보면 차이점이 어느정도 와 닿기는 한다
리버팟
리버팟이란?
Dart/Flutter를 위한 provider에서 발전된 형태의 동적인 캐시관리할 수 있는 프레임워크
Provider 패키지를 발전하여 작성되었음.
런타임이 아닌 컴파일타임에 오류를 감지합니다.
리버팟에서의 프로바이더는?
상태를 캡슐화하고 변화에 대하여 감지하고 적용하는 객체입니다.
다양한 위치에서 간편하게 접근 가능합니다.
싱글톤, 서비스 로케이터, 의존성주입, 동적 위젯 같은 여러 패턴들을 대체 가능합니다.
코드 제너레이터
코드 제너레이터는 flutter/dart에서 일반적으로 사용되는 패턴입니다.
리버팟 2.0에 적용되었습니다.
자동으로 사용 환경에 맞는 최적의 프로바이더를 생성하고 정의해 줍니다.
프로바이더의 종류와 정의
함수형 프로바이더(Function Provider)
상대적으로 단순한 형태를 반환하며, 지정된 상태, 단일 데이터의 단순작업에서 사용된다.
Provider
가장 기본적인 형태이다. 반환값으로 명시된 값을 provider 형태로 반환한다. 해당 값은 provider 생태주기에 따라 관리된다.
@riverpod
Repository repository(RepositoryRef ref) {
return Repository();
}
FutureProvider/StreamProvider
Future/Stream형태의 값을 관리하도록 설계된 provider 이다.
(만약 코드 제너레이터를 사용하지 않는다면 )futrue/stream을 반환하는 provider와 기능적으로는 동일하며 혼동 될 수 있다.
하지만 해당 provider를 통해 반환되는 값의 경우 반환받아 사용할 때 차이점을 확인할 수 있다.
프로바이더 자체적으로 future/stream값을 쉽게 관리할 수 있는 추가적인 메서드들을 지원한다.
@riverpod
Future<User> fetchUser(FetchUserRef ref, {required int userId}) async {
final json = await http.get('api/user/$userId');
return User.fromJson(json);
}
클래스 프로바이더(Class Provider)
단순한 값이 아닌 객체단위의 값을 관리한다. 변경이 자주 일어나며 상태 단위로 객체를 관리할 수 있는 provider 이다.
클래스는 state가지며 해당 state 변화가 일어나는 것을 동작의 단위로 결정한다.
값의 변경이 비동기적으로 일어난다는 것은 future/stream과 유사하지만 변경 이벤트의 종류가 다양하며 반환되는 값이 아닌 클래스에 속한 state를 통해서 이후 동작을 결정한다.
NotifierProvider
class Todos extends _$Todos {
@override
List<Todo> build() {
return [];
}
void addTodo(Todo todo) {
state = [...state, todo];
}
}
AsyncNotifierProvider/StreamNotifierProvider (New in 2.0)
class AsyncTodos extends _$AsyncTodos {
Future<List<Todo>> _fetchTodo() async {
final todos = jsonDecode(await http.get('api/todos')) as List<Map<String, dynamic>>;
return todos.map(Todo.fromJson).toList();
}
@override
FutureOr<List<Todo>> build async => fetchTodo();
Future<void> addTodo(Todo todo) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
await http.post('api/todos', todo.toJson());
return _fetchTodo();
});
}
}
Lagacy
StateNotifierProvider
StateProvider
ChangeNotifierProvider
프로바이더 사용방법
위젯 혹은 프로바이더의 ref 객체를 통해서 다른 프로바이더에 접근할 수 있다.
watch
프로바이더의 상태를 지속적으로 관찰한다. 위젯 혹은 프로바이더가 변경되는 경우 실행된다.
변경된 상태 를 반환받는다.
listen
프로바이더의 상태변화를 감지한다.. 프로바이더의 변경 시 콜백을 설정하여 실행한다.
변경 이벤트(변경 전 상태와 변경 후 상태)를 반환받는 콜백을 설정한다.
read
프로바이더의 현제 상태를 읽어들인다. 한번만 호출된다.
ConsumerWidget
riverpod의 상태변화를 위젯(UI)에서 더 쉽게 사용하기 위해 사용되는 위젯이다.
해당 위젯은 build 시 WidgetRef 를 함께 받으며 해당 ref를 통해 프로바이더를 사용할 수 있다.
해당 ref가 watch로 구독하고 있는 프로바이더가 rebuild되는 경우 해당 위젯을 rebuild한다.
read, listen의 경우 rebuild는 되지 않는다.
프로바이더의 동작
설정 된 프로바이더는 Global Scope에 등록되어 관리된다.
만약 해당 프로바이더를 watch하는 ref가 없다면 자동으로 dispose된다.
April Flutter Meetup - Riverpod Introduction - Speaker Deck
April Flutter Meetup - Riverpod Introduction
speakerdeck.com
Riverpod
A boilerplate-free and safe way to share state
riverpod.dev