본문으로 바로가기
  1. Home
  2. Flutter/Package
  3. Flutter Riverpod All Providers (5-1) - Provider

Flutter Riverpod All Providers (5-1) - Provider

· 댓글개 · Dev_Whale

Provider

Provider는 모든 Provider 중 가장 기본이 되는 것입니다. 값을 생성합니다... 그게 전부입니다.

 

Provider는 일반적으로 다음과 같은 목적으로 사용됩니다:

  • 계산 캐싱
  • 다른 공급자(예: 리포지토리/HttpClient)에 값 노출.
  • 테스트 또는 위젯이 값을 재정의할 수 있는 방법을 제공합니다.
  • select를 사용하지 않고도 provider/위젯의 rebuild를 줄일 수 있습니다.

Using Provider to cache computations

Provider는 ref.watch와 함께 사용하면 동기식 작업을 캐싱하는 강력한 도구입니다.

 

예를 들어 할 일 목록을 필터링할 수 있습니다. 목록을 필터링하는 데 약간의 비용이 들 수 있으므로 애플리케이션이 다시 렌더링할 때마다 할 일 목록을 필터링하지 않는 것이 이상적입니다. 이 상황에서는 Provider를 사용하여 필터링을 수행할 수 있습니다.

 

이를 위해 애플리케이션에 할 일 목록을 처리하는 기존 NotifierProvider가 있다고 가정합니다:

class Todo {
  Todo(this.description, this.isCompleted);
  final bool isCompleted;
  final String description;
}

class TodosNotifier extends Notifier<List<Todo>> {
  @override
  List<Todo> build() {
    return [];
  }

  void addTodo(Todo todo) {
    state = [...state, todo];
  }
  // TODO add other methods, such as "removeTodo", ...
}

final todosProvider = NotifierProvider<TodosNotifier, List<Todo>>(() {
  return TodosNotifier();
});

거기에서 Provider를 사용하여 필터링된 할 일 목록을 제공하여 완료된 할 일만 표시할 수 있습니다:

final completedTodosProvider = Provider<List<Todo>>((ref) {
  // We obtain the list of all todos from the todosProvider
  final todos = ref.watch(todosProvider);

  // we return only the completed todos
  return todos.where((todo) => todo.isCompleted).toList();
});

이 코드를 사용하면 이제 completedTodosProvider를 수신하여 완료된 할 일의 목록을 표시할 수 있습니다:

Consumer(builder: (context, ref, child) {
  final completedTodos = ref.watch(completedTodosProvider);
  // TODO show the todos using a ListView/GridView/...
});

흥미로운 부분은 이제 목록 필터링이 캐시된다는 점입니다.

즉, 완료된 할 일 목록을 여러 번 읽더라도 할 일이 추가/제거/업데이트될 때까지 완료된 할 일 목록이 다시 계산되지 않는다는 뜻입니다.

할 일 목록이 변경될 때 캐시를 수동으로 비활성화할 필요가 없다는 점에 유의하세요. Provider는 ref.watch 덕분에 결과를 다시 계산해야 하는 시기를 자동으로 알 수 있습니다.

Provider를 사용하여 provider/widget rebuild 줄이기

Provider의 고유한 특징은 Provider가 다시 계산되더라도(일반적으로 ref.watch를 사용할 때) 값이 변경되지 않는 한 이를 수신하는 widget/provider를 업데이트하지 않는다는 점입니다.

 

실제 예로는 페이지 지정된 보기의 이전/다음 버튼을 활성화/비활성화하는 경우를 들 수 있습니다:

여기서는 특히 "이전" 버튼에 초점을 맞추겠습니다.
이러한 버튼을 단순하게 구현하면 현재 페이지 인덱스를 가져오는 위젯이 될 수 있으며, 인덱스가 0이면 버튼을 비활성화할 수 있습니다.

 

이 코드일 수 있습니다:

final pageIndexProvider = StateProvider<int>((ref) => 0);

class PreviousButton extends ConsumerWidget {
  const PreviousButton({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 첫 페이지가 아닌 경우 이전 버튼이 활성화되어 있습니다.
    final canGoToPreviousPage = ref.watch(pageIndexProvider) != 0;

    void goToPreviousPage() {
      ref.read(pageIndexProvider.notifier).update((state) => state - 1);
    }

    return ElevatedButton(
      onPressed: canGoToPreviousPage ? goToPreviousPage : null,
      child: const Text('previous'),
    );
  }
}

이 코드의 문제점은 현재 페이지를 변경할 때마다 "이전" 버튼이 다시 빌드된다는 것입니다.
이상적으로는 활성화와 비활성화 사이를 변경할 때만 버튼이 다시 빌드되기를 원할 것입니다.

 

여기서 문제의 근원은 '이전' 버튼 내에서 사용자가 이전 페이지로 바로 이동할 수 있는지 여부를 계산하고 있다는 점입니다.

 

이 문제를 해결하는 방법은 이 로직을 위젯 외부에서 Provider로 분리하는 것입니다:

final pageIndexProvider = StateProvider<int>((ref) => 0);

// 사용자가 이전 페이지로 이동할 수 있는지 여부를 계산하는 provider
final canGoToPreviousPageProvider = Provider<bool>((ref) {
  return ref.watch(pageIndexProvider) != 0;
});

class PreviousButton extends ConsumerWidget {
  const PreviousButton({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 이제 새로운 Provider 위젯이 더 이상 
    // 이전 페이지로 이동할 수 있는지 여부를 계산하지 않습니다.
    final canGoToPreviousPage = ref.watch(canGoToPreviousPageProvider);

    void goToPreviousPage() {
      ref.read(pageIndexProvider.notifier).update((state) => state - 1);
    }

    return ElevatedButton(
      onPressed: canGoToPreviousPage ? goToPreviousPage : null,
      child: const Text('previous'),
    );
  }
}

간단한 리팩터링을 수행하면 Provider 덕분에 페이지 인덱스가 변경될 때 이전 버튼 위젯이 더 이상 다시 빌드되지 않습니다.

 

이제부터 페이지 인덱스가 변경되면 canGoToPreviousPageProvider provider가 다시 계산됩니다. 그러나 provider가 제공한 값이 변경되지 않으면 PreviousButton은 다시 빌드되지 않습니다.

 

이 변경은 버튼의 성능을 개선하는 동시에 위젯 외부에서 로직을 추출하는 흥미로운 이점을 가져다주었습니다.

💬 댓글 개
이모티콘창 닫기
울음
안녕
감사해요
당황
피폐

이모티콘을 클릭하면 댓글창에 입력됩니다.