[Flutter] riverpod 2.0에 대한 이해

홍석·2023년 9월 15일

해당 게시글은 2023-09-15 기준으로
riverpod-v2 공식문서를 읽어보며 이해한 내용입니다.

다음글 - riverpod 2.0 사용기 - 2

왜 Riverpod인가

클라이언트 단에서 네트워크 요청에서 고려해야할 사항

  • UI는 요청이 수행되는 동안 로드 상태를 표시해야한다
  • 오류는 정상적으로 처리해야한다
  • 가능하다면 요청을 캐시해야 한다.

Provider의 이점 - 일반함수처럼 작동한다

  • 캐시 기능 제공
  • 기본 오류/로딩 기능 제공
  • listenable 기능 제공
  • 일부 데이터가 변경시 자동으로 실행

따라서 provider은 GET 요청에 완벽하게 적합


사용법

Provider를 정의하는 구문

final providerName = SomeProvider.someModifier<Result>((ref){
	//my_logic
    return Result();
});

Provider type : 주로 Provider, NotifierProvider, AsyncNotifierProvider, FutureProvider

Provider: 상태변경 없을때 사용
NotifierProvider: 상태변경 있을시 사용
FutureProvider: Future이면서 상태변경 없을때 사용
AsyncNotifierProvider: Future이면서 상태변경 있을때 사용

Modifiers : 선택사항 autoDispose, family

autoDispose : provider가 사용을 중지할 때, 캐시를 자동으로 지운다
family : provider에게 인수를 전달한다.


POST 요청을 다루는 방법은 무엇인가

Provider은 상태를 수정할 수 있는 방법을 노출하지 않는다.
이는 상태를 제한된 방식으로만 수정되고, 목적을 분리하기 위함이다.

그러므로 Provider를 상태를 수정하는 방법을 명시적으로 노출해야 한다.
이를 위해 Notifier를 사용하는 것이다.

Notifier를 정의하는 구문

final providerName = SomeNotifierProvider.someModifier<MyNotifier, Result>(MyNotifier.new);
 
class MyNotifier extends SomeNotifier<Result> {
  
  Result build() {
    <your logic here>
  }
  <your methods here>
}

! 주로 API 통신시, AsyncNotifierProvider를 가장 많이 사용할 것이다.

Notifier의 생성자

The parameter of "notifier providers" is a function which is expected to instantiate the "notifier".
It generally should be a "constructor tear-off".

이전의 StateNotifer와는 다르게 생성자를 받지 않는다.

Notifier 클래스의 메소드

이 해당 클래스를 통해 Provider의 상태를 수정하는 방법을 노출시킨다.
해당 메서드를 사용하려면 ref.read(someProvider.notifier).method();
와 같은방법으로 일반적인 provider와 동일하게 사용한다.

! Notifier은 state를 제외한 다른 속성은 없어야 하며, UI는 state가 변경되었음을 알수 없다

Notifier 클래스 유형

NotifierProvider 사용하려면 -> Notifier
AsyncNotifierProvider 사용하려면 -> AsyncNotifier
AsyncNotifierProvider.autoDispose 사용하려면 -> AutoDisposeAsyncNotifier
AsyncNotifierProvider.autoDispose.family 사용하려면 -> AutoDisposeFamilyAsyncNotifier

build 메서드 사용

모든 Notifier는 build메서드를 재정의 해야한다.
This method is equivalent to the place where you would normally put your logic in a non-notifier provider.(notifier가 아닌 일반 Provider에서 초기화하기전에 하는 작업을 build 메서드에서 수행한다)
해당 build 메서드를 직접호출해서는 안된다.

Notifier예제

final todoListProvider =
    AsyncNotifierProvider.autoDispose<TodoList, List<Todo>>(
  TodoList.new,
);

// We use an AsyncNotifier because our logic is asynchronous.
// More specifically, we'll need AutoDisposeAsyncNotifier because
// of the "autoDispose" modifier.
class TodoList extends AutoDisposeAsyncNotifier<List<Todo>> {
  
  Future<List<Todo>> build() async {
    // The logic we previously had in our FutureProvider is now in the build method.
    return [
      Todo(description: 'Learn Flutter', completed: true),
      Todo(description: 'Learn Riverpod'),
    ];
  }
}

! 위젯 내에서 Provider를 읽는 방법은 동일하게 ref.watch(todoListProvider)를 사용한다.

class TodoList extends AutoDisposeAsyncNotifier<List<Todo>> {
  
  Future<List<Todo>> build() async => [/* ... */];

  Future<void> addTodo(Todo todo) async {
    await http.post(
      Uri.https('your_api.com', '/todos'),
      // We serialize our Todo object and POST it to the server.
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(todo.toJson()),
    );
  }
}

해당 addTodo를 구현하여 POST 메서드를 사용한다.
ref.read(todoListProvider.notifier)
.addTodo(Todo(description: 'This is a new todo'));

Future<void> addTodo(Todo todo) async {
  // The POST request will return a List<Todo> matching the new application state
  final response = await http.post(
    Uri.https('your_api.com', '/todos'),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode(todo.toJson()),
  );

  // We decode the API response and convert it to a List<Todo>
  List<Todo> newTodos = (jsonDecode(response.body) as List)
      .cast<Map<String, Object?>>()
      .map(Todo.fromJson)
      .toList();

  // We update the local cache to match the new state.
  // This will notify all listeners.
  state = AsyncData(newTodos);
}

서버로 부터 응답이 올시 이를 state에 반영한다.
서버로 부터 응답값이 오지않을시? 해당 데이터가 비지니스 로직에서 치명적인 데이터가 아닐시
로컬환경에서 Optimistic Response을 하여
state = [...newTodos, ...state];의 방식을 사용하기도 한다.

다른 방법으로는 AsyncNotifier를 다시 빌드하기 위해 ref.invalidateSelf();하는 방법을 취한다.

Future<void> addTodo(Todo todo) async {
  // We don't care about the API response
  await http.post(
    Uri.https('your_api.com', '/todos'),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode(todo.toJson()),
  );

  // Once the post request is done, we can mark the local cache as dirty.
  // This will cause "build" on our notifier to asynchronously be called again,
  // and will notify listeners when doing so.
  ref.invalidateSelf();

  // (Optional) We can then wait for the new state to be computed.
  // This ensures "addTodo" does not complete until the new state is available.
  await future;
}

Reference

https://docs-v2.riverpod.dev/

profile
bayy1216.tistory.com <- 블로그 이전했습니다 🥹

0개의 댓글