flutter

riverpod2.0 codegeneration 학습 2

bakerlee 2023. 5. 6. 12:12

class 기반의 riverpod은 몇가지의 특징을 가진다. 

우선 g.dart파일로 생성된 결과물은 Notifier / AsyncNotifier를 생성하는 것을 볼 수 있다. 

AutoDisposeNotifier<dynamic>;

AutoDisposeAsyncNotifier<dynamic>;

 

1. 생성 

NotidierProvider

notifier는 state를 가지고 있으며, 해당 state를 중심으로 동작한다. 

notifier는 반드시 class로 구성되어야 하며, codegenerater를 사용한다면  다음과 같이 상속을 통해 구현됨을 명시하여야 한다. 

@riverpod
class ValueNotifier extends _$ValueNotifier

ChangeNotifier 혹은 StateNotifier를 구현하는 것과 일정부분 유사하다.

해당 notifier는 반드시 build를 상속받아 구현하여야 하며, 이 build의 반환타입이 state의 타입으로 결정된다.

또한 state가 변경되는 경우 변경을 발행한다. 

@riverpod
class ValueNotifier extends _$ValueNotifier {
  @override
  int build() {
    /// initial

    Logger().i("build Value Notifier");
    return 0;
  }
  
  void increment() {
    state++;
  }

  void decrement() {
    state--;
  }
}

override 받은 build 내부의 값에 따라 최초의 상태가 결정되고 반환된다. 
해당 provider의 경우 int 로 명시를 하였기 때문에 state가 int 타입으로 선언되어 있다.    
state의 타입은 특수한 제한이 걸려있지 않으며 state 전용 class를 설정할 수도 있으며, 값을 명시하지 않을 경우 dynamic으로 설정된다.  

AsyncNotifier 

만약 반환값을 비동기 타입(future/ stream) 으로 명시하는 경우 AsyncNotifier 를 생성한다. 

@riverpod
class FutureNotifier extends _$FutureNotifier {
  int _counter = 0;

  @override
  FutureOr<int> build() async {
    await Future.delayed(Duration(seconds: 1));

    return _counter;
  }

  void increment() async {
    state = const AsyncValue.loading();
    _counter++;
    await Future.delayed(Duration(seconds: 1));
    state = AsyncValue.data(_counter);
  }

  void decrement() async {
    state = const AsyncValue.loading();
    _counter--;
    await Future.delayed(Duration(seconds: 1));
    state = AsyncValue.data(_counter);
  }
}

 

일반적인 특징은 위의 notifier와 큰 차이가 없으며 비동기를 기반으로 생성되는 특징이 있다. 

단, 해당 state를 받아서 사용하는 경우 비동기처리를 수월하게 할 수 있는 여러 메서드들을 제공한다.  

 

 

2. 사용 

일반적인 provider의 사용과 같이 ref.watch/ read/ listen을 사용하여 값을 읽을 수 있다. 

class _ValueNotifierScreen extends ConsumerWidget {
  const _ValueNotifierScreen({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final valueNotifier = ref.watch(valueNotifierProvider.notifier);
    final valueState = ref.watch(valueNotifierProvider);

    return Scaffold(
      appBar: AppBar(
        title: Text("value notifier"),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Center(
            child: Text("state is $valueState"),
          ),
          ElevatedButton(
              onPressed: () {
                valueNotifier.increment();
              },
              child: const Text("increment")),
          ElevatedButton(
              onPressed: () {
                valueNotifier.decrement();
              },
              child: const Text("decrement")),
        ],
      ),
    );
  }
}

위의 코드를 본다면 

final valueNotifier = ref.watch(valueNotifierProvider.notifier);
final valueState = ref.watch(valueNotifierProvider);

해당 코드가 의문스러울수 있다.
.norifier를 통해 사용하는 범위가 다를 수 있다. 
notifier는 구현하는 class 전체를 사용할 수 있으며, state는 말 class내부의 state만 접근 가능하다. 

이렇게 본다면 notifier만 사용하면되지 왜  state를 따로 받는지 의문스러울 수 있다. 

이 두 용법의 차이는 state 변경 시 widget이 rebuild되는가 되지 않는가 이 차이가 있다. 

notifier는 state가 변경되어도 해당 widget을 rebuild시키지 않는다.
valueNotifier.state를 통해 값을 받을 시 값이 갱신됨은 확인 가능하지만 widget은 자동으로 갱신되지 않는다. 

따라서 값의 변경에 따라 UI가 변경되는 것을 구현하고 싶다면 notifier없는 provier를 구독하여야 한다. 

class 내부의 메서드들을 실행시키고 싶다면 notifier를  값의 변경에 따라 UI를 rebuild를 하고 싶다면 state를 받아야 한다. 

 

AsyncNotifier의 경우 다음과 같이 사용한다. 

class _FutureValueNotifierScreen extends ConsumerWidget {
  const _FutureValueNotifierScreen({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final valueNotifier = ref.watch(futureNotifierProvider.notifier);
    final valueState = ref.watch(futureNotifierProvider);

    /// state

    return Scaffold(
      appBar: AppBar(
        title: Text("future value notifier"),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Center(
            child: valueState.when(
              data: (data) => Text("value is $data"),
              error: (error, stackTrace) => Text("error"),
              loading: () => CircularProgressIndicator(),
            ),
          ),
          ElevatedButton(
              onPressed: () {
                valueNotifier.increment();
              },
              child: const Text("increment")),
          ElevatedButton(
              onPressed: () {
                valueNotifier.decrement();
              },
              child: const Text("decrement")),
        ],
      ),
    );
  }
}

 

다른 부분의 차이는 없으나  

child: valueState.when(
  data: (data) => Text("value is $data"),
  error: (error, stackTrace) => Text("error"),
  loading: () => CircularProgressIndicator(),
),

와 같이 state 자체에서 비동기 상태에 따른 처리를 할 수 있도록 보조해주는 것을 확인할 수 있다. 
정확히는 AsyncBuilder를 자동으로 구현해 준다고 볼 수 있다. 

 

 

3. 여담 

rivierpod 역시 공통적으로 작성해야 하는 코드들이 어느정도 있는 편이다. 

intelij나 vscode의 플러그인을 통해 codesnippets를 사용할 수 있다. 

Flutter Riverpod Snippets - Visual Studio Marketplace

 

Flutter Riverpod Snippets - Visual Studio Marketplace

Extension for Visual Studio Code - Quick and easy Flutter Riverpod snippets

marketplace.visualstudio.com

Flutter Riverpod Snippets - IntelliJ IDEs Plugin | Marketplace (jetbrains.com)

 

Flutter Riverpod Snippets - IntelliJ IDEs Plugin | Marketplace

Flutter Riverpod live templates is a way to enhance the way you use Riverpod. It contains a collection of different snippets such as family or provider. Snippets...

plugins.jetbrains.com

 

 

 

BowonLee/state_management_practice: 상태관리 라이브러리 관련 학습 (github.com)

 

GitHub - BowonLee/state_management_practice: 상태관리 라이브러리 관련 학습

상태관리 라이브러리 관련 학습. Contribute to BowonLee/state_management_practice development by creating an account on GitHub.

github.com

시작하기 | Riverpod

 

시작하기 | Riverpod

[Riverpod]의 내부 메커니즘에 들어가기 앞서, 우선 [Riverpod]을 설치하는 방법과

riverpod.dev