본문 바로가기

Development/Flutter

[RiverPod] 2. State disposal

ref.

https://riverpod.dev/docs/essentials/auto_dispose

 

Clearing cache and reacting to state disposal | Riverpod

So far, we've seen how to create/update some state.

riverpod.dev

 

 

Provider.autoDispose 처럼  autoDispose 옵션을 단 경우, 더이상 해당 provider의 state 변화를 추적하는 리스너가 없는 경우 자동으로 state 가 폐기된다.

 

autoDispose 옵션 유무와는 관계없이, state 가 변화할 경우 이전 state 는 폐기된다.

하지만, state 폐기 시 옵션을 추가하여 이전 state 에 대한 엑세스를 할 수도 있다.

Provider 의 인자로 받는 함수 내부에 ref.onDispose() 를 추가하여 Provider state 폐기시에 실행되는 콜백함수를 추가할 수 있다.

주의할 점은, onDispose 로 Provider 의 state를 변경하는 행위는 버그로 이어진다고 설명한다.

You can optionally change this behavior and use automatic disposal. When doing so, Riverpod will track whether a provider has listeners or not. Then, if the provider has no listeners for a full frame, the state will be destroyed.

...

Enabling/disabling automatic disposal has no impact on whether or not the state is destroyed when the provider is recomputed.
The state will always be destroyed when the provider is recomputed.

...

The callback of ref.onDispose must not trigger side-effects. Modifying providers inside onDispose could lead to unexpected behavior.

 

Provider 가 dispose 될 때 어떻게 동작할지는 dispose 당시 리스너가 남아있는지 여부에 따라 달라진다.

(그 provider 를 리스닝 - ref.watch/listen - 하는 다른 consumer위젯, provider, notifier 가  있는지)

- The provider is no longer used and is in "auto dispose" mode (more on that later). In this case, all associated state with the provider is destroyed.
- The provider is recomputed, such as with `ref.watch`. In that case, the previous state is disposed, and a new state is created.

 

리스너 없음 + auto dispose On >> provider 폐기

리스너 없음 + auto dispose off >> provider 그대로 남아있음 (cache)

리스너 있음 (auto dispose 되지 않는 조건) + 강제로 dispose

>> 새 state 를 계산. provider 가 저장한 데이터 캐시가 지워진다. 새로고침 효과.

 

새 state 를 계산한다는 말은 provider 의 초기화가 다시 이뤄진다는 말인 것 같다.

NotifierProvider 의 경우 Notifier 의 build 메서드가 재실행되는 효과가 있었다.

 

강제로 dispose 하는 것은 ref.invalidate / ref.invalidateSelf  or ref.refresh() 를 통해 가능하다.

refresh 는 invalidate + read 와 문법적으로 동일하다고 FAQ 에 설명하고 있다.

https://riverpod.dev/docs/essentials/faq

 

FAQ | Riverpod

Here are some commonly asked questions from the community:

riverpod.dev

 

Invalidate / InvalidateSelf

 

https://riverpod.dev/docs/essentials/auto_dispose

 

Clearing cache and reacting to state disposal | Riverpod

So far, we've seen how to create/update some state.

riverpod.dev

ref.invalidate 의 두 가지 실행 결과

Using 'ref.invalidate' will destroy the current provider state. There are then two possible outcomes:
- If the provider is listened, a new state will be created.
- If the provider is not listened, the provider will be fully destroyed.

ref.invalidateSelf() 에 대한 설명
It is possible for providers to invalidate themselves by using `ref.invalidateSelf`. Although in this case,
<h3> this will always result in a new state being created.</h3>

 

InvalidateSelf 테스트

 

이전 포스팅에서의 예제코드에서 초기화 버튼만 추가했다.

초기화 버튼을 누르면 invalidateSelf() 가 실행된다.

결과적으로 Notifier 의 build 메서드가 재실행되면서 초기화가 이뤄짐을 알 수 있었다.

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class ButtonNotifier extends AutoDisposeAsyncNotifier<int> {
  late int count;
  @override
  FutureOr<int> build() {
    count = 5;
    return count;
  }

  Future<void> pressBtn() async {
    state = const AsyncValue.loading();
    await Future.delayed(const Duration(seconds: 1));
    count++;
    print(count);
    state = AsyncData(count);
  }

  void refresh() {
    ref.invalidateSelf();
  }
}

final buttonProvider = AutoDisposeAsyncNotifierProvider<ButtonNotifier, int>(
  () => ButtonNotifier(),
);

// ###################### view ###################################
class RiverpodTestScreen extends ConsumerStatefulWidget {
  static String routePath = "/lab/test";
  static String routeName = "test";
  const RiverpodTestScreen({super.key});

  @override
  ConsumerState<ConsumerStatefulWidget> createState() =>
      _RiverpodTestScreenState();
}

class _RiverpodTestScreenState extends ConsumerState<RiverpodTestScreen> {
  void _countUp() {
    ref.read(buttonProvider.notifier).pressBtn();
  }

  void _refresh() {
    ref.read(buttonProvider.notifier).refresh();
  }

  @override
  Widget build(BuildContext context) {
    ref.watch(buttonProvider);
    final state = ref.watch(buttonProvider.notifier).state;
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextButton(
              child: Text("state.value: ${state.value}"),
              onPressed: () => _countUp(),
            ),
            TextButton(
              child: const Text("refresh"),
              onPressed: () => _refresh(),
            ),
          ],
        ),
      ),
    );
  }
}

 

reacting to disposal 요약

  • ref.invalidate / invalidateSelf : 강제로 state 폐기. provider 폐기 or 초기화 (캐시 제거) 두 가지 결과.
  • ref.refresh: invalidate + read
  • ref.keepAlive: dispose 지연
  • ref.onCancel: 마지막 리스너가 제거되었을 때
  • ref.onResume: onCancel 이후 첫 리스너가 추가되었을 때