공log/[Flutter]

[Flutter] 플러터 #22 - ListenProvider

ming_OoO 2023. 5. 26. 20:38
728x90

ListenProvider에 대해 알아보겠습니다

 ListenProvider는 Flutter의 상태 관리 패키지 중 하나인 Riverpod에서 제공하는 기능입니다. ListenProvider는 다른 Provider를 구독하고 해당 Provider가 변경될 때마다 리스닝하고, 이를 기반으로 상태를 업데이트하는 Provider입니다. 이를 통해 동적인 데이터 의존성을 관리하고 상태 변화에 따라 적절한 액션을 수행할 수 있습니다.

 

1. 개념:

  • ListenProvider는 다른 Provider의 상태 변화를 감지하여 자동으로 리스닝하고 상태를 업데이트하는 Provider입니다.
  • 다른 Provider의 값을 읽어와 이를 기반으로 새로운 값을 생성하거나 액션을 수행합니다.
  • 다른 Provider와 의존성을 가지며, 해당 Provider가 변경될 때마다 리스닝하여 상태를 업데이트합니다.

 

2. 장점:

  • 동적인 데이터 의존성 관리: ListenProvider는 다른 Provider에 의존하므로, 해당 Provider가 변경될 때 자동으로 상태를 업데이트할 수 있습니다.
  • 간편한 상태 관리: ListenProvider를 사용하면 상태 변화에 따라 적절한 액션을 수행할 수 있으므로, 복잡한 상태 관리 로직을 간단하게 구현할 수 있습니다.
  • 확장성과 유연성: 다양한 Provider를 조합하여 ListenProvider를 사용할 수 있으며, 필요에 따라 상태 변화를 처리하는 로직을 커스터마이즈할 수 있습니다.

 

3. 사용 방법:

  • ListenProvider를 생성할 때, `ref.listen` 메서드를 사용하여 리스닝할 Provider를 지정합니다.
  • `ref.listen` 메서드는 ProviderContainer의 참조를 통해 다른 Provider를 읽을 수 있습니다.
  • 리스닝할 Provider가 변경될 때마다 제공된 콜백 함수가 호출되어 상태를 업데이트합니다.
  • 상태 업데이트는 `ref.read` 또는 `ref.watch`를 통해 필요한 다른 Provider 값을 읽어와 처리합니다.
import 'package:flutter_riverpod/flutter_riverpod.dart';

// 그냥 listen 형태의 StateProvider 선언
final listenProvider = StateProvider<int>((ref) => 0);
  • listenProviderStateProvider로 선언된 Provider입니다. 초기값으로 0을 가지고 있습니다.
import 'package:ex_state/riverpod/listen_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class ListenProviderScreen extends ConsumerStatefulWidget {
  const ListenProviderScreen({Key? key}) : super(key: key);

  @override
  ConsumerState<ListenProviderScreen> createState() =>
      _ListenProviderScreenState();
}

class _ListenProviderScreenState extends ConsumerState<ListenProviderScreen>
    with TickerProviderStateMixin {
  late final TabController controller;

  @override
  void initState() {
    super.initState();

    // initState 단계에서 TabBarView의 컨트롤러 생성
    controller = TabController(
      length: 10,
      vsync: this,

      // 문제점이 하나 있다. 여기서 따로 설정을 안 해줄 경우 해당 페이지를 다시 로드할때 맨 처음 StateProvider에서 가리키는 0의 값으로 호출되기에
      // initState에서도 StateProvider의 값을 read 해줄 수 있어야함    *주의* initState는 한번만 실행되기에 watch 함수를 사용해선 X
      initialIndex: ref.read(listenProvider),
      // 초기 인덱스값 지정, 만약 listenProvider를 autoDispose로 해놨다면 매번 0으로 돌아갈 것
    );
  }

  @override
  Widget build(BuildContext context) {

    // 원리: 다음, 뒤로 버튼이 눌릴 때마다 listenProvider의 값이 변경되면서 밑의 listen 함수를 호출하는 것
    // <int> <- 제네릭 선언으로 받는 데이터(previous, next)의 데이터 타입을 미리 선언해줄 수 있음
    // previous: 바뀌기 이전의 상태, next: 바뀐 후의 상태
    ref.listen<int>(listenProvider, (previous, next) {
      // 만약 previous != next 라면 최대 페이지에 도달했다는 뜻이기에
      // ex) (9 == 9) -> False 또는 (0 == 0) -> False
      if (previous != next) {
        controller.animateTo(
          next,
        );
      }
    });

    return DefaultLayout(
      title: 'ListenProviderScreen',
      body: TabBarView(
        controller: controller,
        children: List.generate(
          10,
          (index) => Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(index.toString()),
              ElevatedButton(
                onPressed: () {
                  ref
                      .read(listenProvider.notifier)
                      .update((state) => state == 9 ? 9 : state + 1);
                  // listenProvider.notifier의 상태를 읽어서 state의 값(현재 페이지)이 마지막 페이지 수인 9을 가리키면
                  // 9로 두고 아니면 1 더하는 형태
                },
                child: Text('다음'),
              ),
              ElevatedButton(
                onPressed: () {
                  ref
                      .read(listenProvider.notifier)
                      .update((state) => state == 0 ? 0 : state - 1);
                  // listenProvider.notifier의 상태를 읽어서 state의 값(현재 페이지)이 처음 페이지 수인 0을 가리키면
                  // 0으로 두고 아니면 1 빼는 형태
                },
                child: Text('뒤로'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
  • ListenProviderScreen 위젯은 ConsumerStatefulWidget을 상속하고, ConsumerState<ListenProviderScreen>을 상속하는 _ListenProviderScreenState 클래스를 가집니다.
  • _ListenProviderScreenState 클래스는 TickerProviderStateMixin을 사용하여 애니메이션을 처리할 수 있는 특징을 가지고 있습니다.
  • TabControllerinitState 메서드에서 초기화하고, listenProvider 값을 초기 인덱스로 설정합니다. 이 때, ref.read(listenProvider)를 사용하여 초기 인덱스 값을 읽어옵니다.
  • ref.listen<int>(listenProvider, (previous, next) { ... })를 사용하여 listenProvider의 값 변화를 감지합니다. previous는 변경되기 이전의 상태, next는 변경된 후의 상태를 나타냅니다. 값이 변경될 때마다 controller를 해당 인덱스로 애니메이션을 이동시킵니다.
  • TabBarViewTabController를 사용하여 페이지를 표시하고, 각 페이지마다 해당 인덱스를 표시하고 "다음"과 "뒤로" 버튼을 제공합니다. 
  • "다음" 버튼과 "뒤로" 버튼을 누를 때마다 listenProvider의 값을 업데이트하여 다음 페이지나 이전 페이지로 이동합니다.

다음은 실행결과입니다.


  • ListenProviderScreen은 10개의 페이지를 가지며, 각 페이지마다 페이지 번호와 "다음"과 "뒤로" 버튼이 표시됩니다.
  • 처음 실행했을 때는 0번 페이지가 표시됩니다.
  • "다음" 버튼을 누르면 페이지 번호가 증가하고, "뒤로" 버튼을 누르면 페이지 번호가 감소합니다.
  • listenProvider의 값 변화를 감지하여 해당 페이지 번호에 맞는 애니메이션을 수행합니다.

ListenProvider는 다른 Provider의 상태 변화를 감지하고, 해당 변화에 따라 상태를 업데이트하고 액션을 수행하는 역할을 합니다. 위의 예시에서는 listenProvider의 값 변화에 따라 페이지 번호와 애니메이션을 조정하여 사용자에게 적절한 페이지를 보여줍니다. 이를 통해 동적인 상태 관리와 상태 간의 의존성을 처리할 수 있습니다.

 

ListenProvider는 상태 변화에 따라 동적인 의존성 관리와 상태 업데이트를 수행하는 강력한 도구입니다. 다른 Provider를 리스닝하여 상태를 감지하고, 필요한 액션을 수행할 수 있으므로 복잡한 상태 관리를 간편하게 구현할 수 있습니다.

 

728x90