---
# System prepended metadata

title: Riverpod 小部件刷新
tags: [Flutter]

---

Riverpod 小部件刷新
===

從零開始
===

## 相關網站
- [Riverpod 官網](https://riverpod.dev/)
- [Riverpod Pub.dev](https://pub.dev/packages/riverpod)

## 安裝套件 (riverpod_generator)
在 terminal 輸入 (terminal 可以整段一次輸入)
```cmd
flutter pub add flutter_riverpod
flutter pub add riverpod_annotation
flutter pub add dev:riverpod_generator
flutter pub add dev:build_runner
flutter pub add dev:custom_lint
flutter pub add dev:riverpod_lint
```

## 新增 Riverpod 自定義分析器
在 analysis_options.yaml 新增

```yaml
analyzer:
  plugins:
    - custom_lint
```

## 新增 Riverpod Plugin
Android Studio -> Settings... -> Plugins -> Marketplace -> Flutter Riverpod Snippets -> Install

前置內容
===

## 代碼自動生成 (build_runner)
一次性生成，適用於修改部分邏輯，commit 前執行一次

```cmd
dart run build_runner build
```

持續生成，適用於日常開發 (`Command(⌘) + S` 可觸發生成，`Conrtol(⌃) + C` 可終止腳本)

```cmd
dart run build_runner watch
```

## Riverpod 作用範圍
```dart=
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  // Riverpod 作用範圍必須用 ProviderScope 包起來
  runApp(ProviderScope(child: const MyApp()));
}
```

## 建立 Provider
使用 @riverpod 自動生成 Provider 需在 import 下方手動新增 part 'xxx.g.dart'，xxx 必須與檔名相同，.g 檔案由 build_runner 自動生成 (請勿手動修改)

**test_func.dart**
```dart=
import 'package:riverpod_annotation/riverpod_annotation.dart';

// 需手動新增
part 'test_func.g.dart';

// 會自動生成 [testMessageProvider]
@riverpod
String testMessage(Ref ref) {
  return 'Test Message';
}
```

## 使用 Provider
單純使用 Provider 不需新增 part

**test_widget.dart**
```dart=
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 讀取 testMessageProvider 參數
    final message = ref.read(testMessageProvider);
    return Text(message);
  }
}
```

## 善用套件生成範本

### Provider
![plugin_provider_generator](https://hackmd.io/_uploads/rJWBJlH0ee.gif)

:::info
由於生成範本未適配最新版 Riverpod，需手動將 `TestRef` 改為 `Ref`
:::

### NotifierProvider
![plugin_notifier_provider_generator](https://hackmd.io/_uploads/r1zUEgrAlx.gif)

### ConsumerStatefulWidget
![plugin_consumer_generator](https://hackmd.io/_uploads/S1fZ0lBAlg.gif)

:::info
IDE 補全 import 快捷為 `Option(⌥) + Enter(⏎)`
:::

主要內容
===

## 1. 讀取型別
回傳型別決定了 provider 能讀取到的型別

### 1-1. Provider
```dart=
/// [test1Provider] type is int
// 回傳 int 決定了 provider 能讀取到 int
@riverpod
int test1(Ref ref) => 1;
```

### 1-2. Notifier Provider
```dart=
/// [test2Provider] type is bool
@riverpod
class Test2 extends _$Test2 {
  // 回傳 bool 決定了 provider 能讀取到 bool
  @override
  bool build() => false;
}
```

## 2. 讀取方式

### 2-1. Sync Provider
不需等待，可立即讀取參數

```dart=
/// [test3Provider] type is bool
@riverpod
bool test3(Ref ref) => false;
```

可直接讀取回傳型別

```dart=
void test3Func(Ref ref) {
  final value = ref.read(test3Provider);
  // value type is bool
  if (value) {...}
}
```

### 2-2. Async Provider
需要等待，無法立即讀取參數

```dart=
/// [test4Provider] type is AsyncValue<bool>
@riverpod
FutureOr<bool> test4(Ref ref) => false;

/// [test5Provider] type is AsyncValue<bool>
@riverpod
Future<bool> test5(Ref ref) async => false;

/// [test6Provider] type is AsyncValue<bool>
@riverpod
Stream<bool> test6(Ref ref) async* { yield false; }

/// [test7Provider] type is AsyncValue<bool>
@riverpod
AsyncValue<bool> test7(Ref ref) => const AsyncData(false);
```

以上全部都會讀取到 `AsyncValue<回傳型別>`

```dart=
void test4Func(Ref ref) {
  final value = ref.read(test4Provider);
  // value type is AsyncValue<bool>
  ❌ if (value) {...} // AsyncValue 使用方法後續會提及
}
```

也可使用 `.future` 等待讀取回傳型別

```dart=
Future<void> test5Func(Ref ref) async {
  final value = await ref.read(test4Provider.future);
  // value type is bool
  if (value) {...}
}
```

### 2-3. AsyncValue
`AsyncValue` 被 `AsyncData`、`AsyncError`、`AsyncLoading` 共 3 種狀態繼承，以 `Future` 來說，讀取瞬間若 `Future` 尚未完成會觸發 `AsyncLoading`，讀取瞬間若 `Future` 已完成會觸發 `AsyncData`，且 `AsyncData` 會帶有原始回傳型別

:::info
以下會同時列出 ==過時寫法== 以及 ==主流寫法==，大部分舊專案都用 ==過時寫法==，至少要看得懂，Dart 3.0 後建議優先用 ==主流寫法==
:::

#### 2-3-1. 過時寫法 (.when)
- 優點：IDE 可幫忙生成範本，且支援 `.whenData`、`skipLoadingOnReload`、`skipLoadingOnRefresh`、`skipError` 等特殊用法

`.when` 需在每一種狀態決定回傳參數

```dart=
void test6Func(Ref ref) {
  final value = ref.read(test4Provider);
  // value type is AsyncValue<bool>

  // .when 處理所有狀態
  final newValue = value.when(
    data: (data) => data, // data type is bool
    error: (error, stackTrace) => false,
    loading: () => false,
  );
  // newValue type is bool
}
```

`.when` 可以任意轉換回傳型別，也有 `.whenOrNull`、`.maybeWhen` 各種延伸使用方法

```dart=
void test7Func(Ref ref) {
  final value = ref.read(test4Provider);
  // value type is AsyncValue<bool>

  // 所有狀態都回傳 String
  final newValue1 = value.when(
    data: (data) => '$data', // data type bool -> String
    error: (error, stackTrace) => 'error',
    loading: () => 'loading',
  );
  // newValue1 type is String

  // 不同狀態回傳 2 種以上型別
  final newValue2 = value.when(
    data: (data) => '$data',
    error: (error, stackTrace) => error,
    loading: () => 123,
  );
  // newValue2 type is Object

  // 只處理指定狀態, 其餘狀態回傳 null
  final newValue3 = value.whenOrNull(
    data: (data) => '$data',
    loading: () => 'loading',
  );
  // newValue3 type is String?

  // 只處理指定狀態, 其餘狀態執行 orElse
  final newValue4 = value.maybeWhen(
    data: (data) => 111,
    error: (error, stackTrace) => 222,
    orElse: () => 333,
  );
  // newValue4 type is int

  // data, loading 做相同處理, 針對 error 做不同處理
  final newValue5 = value.when(
    data: (data) => 'data or loading',
    error: (error, stackTrace) {
      if (value.hasValue) {
        return 'error value: ${value.requireValue}';
      } else if (error is StateError) {
        return 'StateError message: ${error.message}';
      } else {
        return 'others error: $error';
      }
    },
    loading: () => 'data or loading',
  );
  // newValue5 type is String
}
```

#### 2-3-2. 主流寫法 (switch-case)
- 優點：官方推薦，強大的 dart switch-case 功能

基本用法

```dart=
void test8Func(Ref ref) {
  final value = ref.read(test4Provider);
  // value type is AsyncValue<bool>
  final newValue = switch (value) {
    AsyncData(:final value) => value,
    AsyncError() => false,
    AsyncLoading() => false,
  };
  // newValue type is bool
}
```

用 switch-case 處理上述過時寫法

```dart=
void test9Func(Ref ref) {
  final value = ref.read(test4Provider);
  // value type is AsyncValue<bool>

  // 所有狀態都回傳 String
  final newValue1 = switch (value) {
    AsyncData(:final value) => '$value', // data type bool -> String
    AsyncError() => 'error',
    AsyncLoading() => 'loading',
  };
  // newValue1 type is String

  // 不同狀態回傳 2 種以上型別
  final newValue2 = switch (value) {
    AsyncData(:final value) => '$value',
    AsyncError(:final error) => error,
    AsyncLoading() => 123,
  };
  // newValue2 type is Object

  // 只處理指定狀態, 其餘狀態回傳 null
  final newValue3 = switch (value) {
    AsyncData(:final value) => '$value',
    AsyncLoading() => 'loading',
    _ => null,
  };
  // newValue3 type is String?

  // 只處理指定狀態, 其餘狀態執行 default
  final newValue4 = switch (value) {
    AsyncData() => 111,
    AsyncLoading() => 222,
    _ => 333,
  };
  // newValue4 type is int

  // data, loading 做相同處理, 針對 error 做不同處理
  final newValue5 = switch (value) {
    AsyncData() || AsyncLoading() => 'data or loading',
    AsyncError() when value.hasValue =>
      'error value: ${value.requireValue}',
    AsyncError(:final StateError error) =>
      'StateError message: ${error.message}',
    AsyncError(:final error) => 'others error: $error',
  };
  // newValue5 type is String

}
```

## 3. 帶參數的 Provider (Family)
讀取時 provider 後方要加 ()，且可以帶入相關參數

### 3-1. Provider
```dart=
/// [test10Provider] type is String
/// 參數定義在 Ref ref 後方
@riverpod
String test10(Ref ref, int value) => 'string from int: $value';

void test10Fun(Ref ref) {
  final result = ref.read(test10Provider(10));
  // result: string from int: 10
}
```

:::warning
Family Provider 可不帶入參數時, 仍必須要加 ()

```dart=
/// [test11Provider] type is String
@riverpod
String test11(Ref ref, {int value = 3}) => 'value is: $value';

void test11Fun(Ref ref) {
  ❌ final result = ref.read(test11Provider);
  ⭕️ final result = ref.read(test11Provider());
  // result: value is: 3
}
```
:::

### 3-2. Notifier Provider
```dart=
/// [test12Provider] type is String
@riverpod
class Test12 extends _$Test12 {
  // 參數定義在 build() 之中
  @override
  String build({required int value1, required int value2}) =>
      '$value1 + $value2 = ${value1 + value2}';
}

void test12Fun(Ref ref) {
  final result = ref.read(test12Provider(value1: 1, value2: 2));
  // result: 1 + 2 = 3
}
```

## 4. Provider vs Notifier Provider
一般 Provider 與 Notifier Provider 最大的差異是 Notifier Provider 可以後期控制自身的參數

### 4-1. Provider
回傳參數後，就無法再發生改變

#### 4-1-1. Sync
```dart=
/// [test13Provider] type is int
@riverpod
int test13(Ref ref) => 1;
```

#### 4-1-2. Async
```dart=
/// [test14Provider] type is AsyncValue<int>
@riverpod
Future<int> test14(Ref ref) async => 1;
```

### 4-2. Notifier Provider
可透過 `state` 變更參數，`state` 參數型別與 return 型別一致 

#### 4-2-1. Sync
```dart=
/// [test15Provider] type is int
@riverpod
class Test15 extends _$Test15 {
  @override
  int build() => 1;

  void addOne(int value) {
    // state type is int
    state = value + 1;
  }
}
```

#### 4-2-2. Async
```dart=
/// [test16Provider] type is AsyncValue<int>
@riverpod
class Test16 extends _$Test16 {
  @override
  Future<int> build() async {
    await Future.delayed(const Duration(seconds: 1));
    return 1;
  }

  Future<void> addTwo(int value) async {
    // state type is AsyncValue<int>
    state = const AsyncLoading();
    await Future.delayed(const Duration(seconds: 1));
    state = AsyncData(value + 2);
  }
}
```

#### 4-2-3. Call Notifier Provder Function
只有 Notifier Provider 有 `.notifier` 方法，讀取 `.notifier` 可拿到 Provider 原始物件，即可透過物件呼叫自定義方法

```dart=
Future<void> test15Func(Ref ref) async {
  final test15 = ref.read(test15Provider.notifier);
  // test15 type is Test15
  test15.addOne(2);
  
  await ref.read(test16Provider.notifier).addTwo(3);
}
```

## 5. read/listen/watch
Provider 參數是可以持續變化的，依據功能需求功能也可以用不同方式讀取

```dart=
/// [test17Provider] type is AsyncValue<int>
@riverpod
Stream<int> test17(Ref ref) async* {
  await Future.delayed(const Duration(seconds: 1));
  yield 1;
  await Future.delayed(const Duration(seconds: 1));
  yield 2;
  await Future.delayed(const Duration(seconds: 1));
  yield 3;
}
// 實際執行:
// AsyncLoading<int>()
// 等 1 秒
// AsyncData<int>(1)
// 等 1 秒
// AsyncData<int>(2)
// 等 1 秒
// AsyncData<int>(3)
```

### 5-1. read
只讀取一次當前參數，後續不再更新

```dart=
void test17Func(Ref ref) {
  final result = ref.read(test17Provider);
  print('result: $result');
}
// 執行結果:
// result: AsyncLoading<int>()
```

:::warning
由於 read 瞬間，test17Provider 剛被創立，故只會讀到 AsyncLoading 參數
:::

### 5-2. listen
持續監聽參數變化，不刷新 ref 自身 (後續會提及)

```dart=
void test18Func(Ref ref) {
  print('start listen');
  ref.listen(test17Provider, (previous, next) {
    print('previous: $previous, next: $next');
  });
}
// 執行結果:
// start listen
// 過 1 秒
// previous: AsyncLoading<int>(), next: AsyncData<int>(value: 1)
// 過 1 秒
// previous: AsyncData<int>(value: 1), next: AsyncData<int>(value: 2)
// 過 1 秒
// previous: AsyncData<int>(value: 2), next: AsyncData<int>(value: 3)
```

:::warning
由於 listen 瞬間，test17Provider 剛被創立，AsyncLoading 為初始值，並非變化，需等到 1 秒後的事件，才會聽到變化
:::

### 5-3. listen (with fireImmediately)
持續監聽參數變化，不刷新 ref 自身 (後續會提及)，且立即回傳當前參數

```dart=
void test19Func(Ref ref) {
  print('start listen');
  ref.listen(test17Provider, (previous, next) {
    print('previous: $previous, next: $next');
  }, fireImmediately: true);
}
// 執行結果:
// start listen
// previous: null, next: AsyncLoading<int>()
// 過 1 秒
// previous: AsyncLoading<int>(), next: AsyncData<int>(value: 1)
// 過 1 秒
// previous: AsyncData<int>(value: 1), next: AsyncData<int>(value: 2)
// 過 1 秒
// previous: AsyncData<int>(value: 2), next: AsyncData<int>(value: 3)
```

:::info
fireImmediately 相當於立即 read 一次，並透過 listen 回傳結果
:::

### 5-4. watch
持續監聽參數變化，且刷新 ref 自身 (後續會提及)

```dart=
void test20Func(Ref ref) {
  final result = ref.watch(test17Provider);
  print('result: $result');
}
// 執行結果:
// result: AsyncLoading<int>()
// 過 1 秒
// result: AsyncData<int>(1)
// 過 1 秒
// result: AsyncData<int>(2)
// 過 1 秒
// result: AsyncData<int>(3)
```

## 6. Ref 主要來源與刷新作用域
`ref.read` 與 `ref.listen` 當參數發生變化時，皆不會觸發 ref 刷新，只有 `ref.watch` 當參數發生變化時，會觸發 ref 刷新，刷新範圍僅提供 ref 本身的物件

### 6-1. Widget (WidgetRef)
`ConsumerWidget`、`ConsumerStatefulWidget`、`Consumer` 是三種最常見的 Widget，拿到的 ref 都屬於 `WidgetRef`

#### 6-1-1. ConsumerWidget
只能透過 `Widget build(BuildContext context, WidgetRef ref)` 方法拿到 ref，當 `ref.watch` 參數變化，會觸發 `build` 刷新

```dart=
class Test21Widget extends ConsumerWidget {
  const Test21Widget({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 只有這裡可拿到 ref, 且 watch 觸發 build 刷新
    final result = ref.watch(someProvider);
    return testWidget(ref);
  }

  Widget testWidget(WidgetRef ref) {
    // 必須倚靠上層帶入, 才能拿到 ref, 且 watch 觸發 build 刷新
    final result = ref.watch(someProvider);
    return Container();
  }
}
```

#### 6-1-2. ConsumerStatefulWidget
`ConsumerState` 有定義 WidgetRef ref，`ConsumerState` 底下都可拿到屬於 Widget 自身的 ref，當 `ref.watch` 參數變化，會觸發 `build` 刷新

```dart=
class Test22Widget extends ConsumerStatefulWidget {
  const Test22Widget({super.key});

  @override
  ConsumerState createState() => _Test22WidgetState();
}

class _Test22WidgetState extends ConsumerState<Test22Widget> {
  @override
  Widget build(BuildContext context) {
    // 可拿到 ref, 且 watch 觸發 build 刷新
    final result = ref.watch(someProvider);
    return Container();
  }

  void test() {
    // 可拿到 ref, 且 watch 觸發 build 刷新
    final result = ref.watch(someProvider);
  }
}
```

#### 6-1-3. Consumer
只能透過 `builder` 方法拿到 ref，當 `ref.watch` 參數變化，會觸發 `builder` 刷新

```dart=
Consumer(builder: (context, ref, child) {
  // 只有這裡可拿到 ref, 且 watch 觸發 builder 刷新
  final result = ref.watch(someProvider);
  return Container();
});
```

:::info
Consumer 通常會被包在其他 Widget 底下，Consumer 的 ref 與 Parent Widget 的 ref 不共用，僅刷新 Consumer 自身的 `builder` 方法，不會觸發 Parent Widget 的 `build` 方法
:::

### 6-2. Provider (Ref)
`Provider`、`Notifier Provider` 拿到的 ref 都屬於 `Ref`

#### 6-2-1. Provider
只能透過方法本身拿到 ref，當 `ref.watch` 參數變化，`方法` 會被重新呼叫

```dart=
@riverpod
bool test23(Ref ref) {
  // 只有這裡可拿到 ref, 且 watch 觸發 test23 方法重新呼叫
  final result = ref.watch(otherProvider);
  return false;
}
```

#### 6-2-2. Notifier Provider
被繼承的 `_$XXX` 有定義 Ref ref，`_$XXX` 底下都可拿到屬於 Notifier 自身的 ref，當 `ref.watch` 參數變化，會觸發 `build` 刷新

```dart=
@riverpod
class Test24 extends _$Test24 {
  @override
  bool build() {
    // 可拿到 ref, 且 watch 觸發 build 刷新
    final result = ref.watch(otherProvider);
    return false;
  }
  
  void test() {
    // 可拿到 ref, 且觸發 build 刷新, 且 watch 觸發 build 刷新
    final result = ref.watch(otherProvider);
  }
}
```

## 7. 生命週期
Provider 會根據 read/listen/watch 或是被 Widget/Provider 讀取，會有不同的生命週期，當 Provider 沒人使用，則會自動銷毀

### 7-1. read
`ref.read` 時，Provider 只會存在一瞬間

- Provider 沒他人使用：Provider 建立 -> read 回傳參數 -> Provider 銷毀

```dart=
Future<void> test25Func(Ref ref) async {
  print('first read');
  ref.read(someProvider);
  print('first read complete');
  await Future.delayed(const Duration(seconds: 1));
  print('second read');
  ref.read(someProvider);
  print('second read complete');
}
// 執行結果:
// first read
// someProvider create
// first read complete
// someProvider dispose
// 過 1 秒
// second read
// someProvider create
// second read complete
// someProvider dispose
```

- Provider 有他人使用：read 回傳當前 Provider 參數
```dart=
Future<void> test26Func(Ref ref) async {
  ref.watch(someProvider); // 持續監聽
  print('first read');
  ref.read(someProvider);
  print('first read complete');
  await Future.delayed(const Duration(seconds: 1));
  print('second read');
  ref.read(someProvider);
  print('second read complete');
}
// 執行結果:
// someProvider create
// first read
// first read complete
// 過 1 秒
// second read
// second read complete
```

### 7-2. WidgetRef listen/watch
`ref.listen`、`ref.watch` 生命週期與 Widget 本身同步，Widget 銷毀，Provider 則同步銷毀

```dart=
Consumer(builder: (context, ref, child) {
  // Consumer 銷毀, someProvider 沒他人使用也會自動銷毀
  final result = ref.watch(someProvider);
  return Container();
});
```

### 7-3. Ref listen/watch
`ref.listen`、`ref.watch` 生命週期與 Provider 同步，當前 Provider 銷毀，另一個 Provider 則同步銷毀

```dart=
@riverpod
bool test27(Ref ref) {
  // test27Provider 銷毀, otherProvider 沒他人使用也會自動銷毀
  final result = ref.watch(otherProvider);
  return false;
}
```

### 7-4. KeepAlive
大部需求都允許 Provider 沒人使用時自動銷毀，少部分需求不希望被銷毀，則需要 KeepAlive 功能

#### 7-4-1. 永久 KeepAlive
`@riverpod` 改為 `@Riverpod(keepAlive: true)`，在第一次讀取時建立，再來永久保持 Provider 不被銷毀

- 適用場景：Singleton

```dart=
@Riverpod(keepAlive: true)
bool test28(Ref ref) => false;

Future<void> test28Func(Ref ref) async {
  print('first read');
  ref.read(test28Provider);
  print('first read complete');
  await Future.delayed(const Duration(seconds: 1));
  print('second read');
  ref.read(test28Provider);
  print('second read complete');
}
// 執行結果:
// first read
// test28Provider create
// first read complete
// 過 1 秒
// second read
// second read complete
```

#### 7-4-2. 暫時 KeepAlive
使用 `ref.keepAlive` 可拿到 `KeepAliveLink` 物件，直到對 `KeepAliveLink` 呼叫 `close()`，Provider 才會被銷毀

- 適用場景：執行耗時任務

```dart=
@riverpod
class Test29 extends _$Test29 {
  @override
  bool build() => false;

  Future<void> upload() async {
    final link = ref.keepAlive();
    state = true;
    await Future.delayed(const Duration(seconds: 2));
    state = false;
    link.close();
  }
}

Future<void> test29Func(Ref ref) async {
  print('first read');
  ref.read(test29Provider.notifier).upload();
  print('first read complete');
  await Future.delayed(const Duration(seconds: 1));
  print('second read');
  ref.read(test29Provider);
  print('second read complete');
}
// 執行結果:
// first read
// test29Provider create
// first read complete
// 過 1 秒
// second read
// second read complete
// 過 1 秒
// test29Provider dispose
```

## 8. 唯一性
Provider 是唯一的，在 Provider 沒有被銷毀前，所有人透過 Provider 拿到的參數都是同一個

### 8-1. Provider
```dart=
@Riverpod(keepAlive: true)
int test30(Ref ref) => Random().nextInt(100);

void test30Func(Ref ref) {
  final result1 = test30Provider == test30Provider;
  print('result1: $result1');
  final result2 = ref.read(test30Provider);
  print('result2: $result2');
  final result3 = ref.read(test30Provider);
  print('result3: $result3');
}
// 執行結果:
// result1: true
// result2: 74
// result3: 74
```

### 8-2. Family Provider
Family Provider 會比對每一個帶入的參數，當每個參數 `==` 皆相同，所有人透過 Provider 拿到的參數才會是同一個

:::warning
請注意 class/List/Map 本身都不符合 `==`，通常 class 都會透過 @freezed 實作 `==` 部分
:::

```dart=
@Riverpod(keepAlive: true)
int test31(Ref ref, {required int value}) => Random().nextInt(100);

void test31Func(Ref ref) {
  final result1 = test31Provider(value: 1) == test31Provider(value: 1);
  print('result1: $result1');
  final result2 = test31Provider(value: 1) == test31Provider(value: 2);
  print('result2: $result2');
  final result3 = ref.read(test31Provider(value: 1));
  print('result3: $result3');
  final result4 = ref.read(test31Provider(value: 1));
  print('result4: $result4');
  final result5 = ref.read(test31Provider(value: 2));
  print('result5: $result5');
}
// 執行結果:
// result1: true
// result2: false
// result3: 15
// result4: 15
// result5: 19
```

## 9. 狀態過濾
Provider 當參數發生變化時，會對上一個狀態進行 `==` 比對，參數不同才會更新狀態

:::warning
請注意 class/List/Map 本身都不符合 `==`，通常 class 都會透過 @freezed 實作 `==` 部分
:::

### 9-1. 基本過濾
```dart=
@riverpod
class Test32 extends _$Test32 {
  @override
  int build() {
    Future(() async {
      await Future.delayed(const Duration(seconds: 1));
      state = 1;
      await Future.delayed(const Duration(seconds: 1));
      // 上一個狀態還是 1, 會被忽略
      state = 1;
      await Future.delayed(const Duration(seconds: 1));
      state = 2;
      await Future.delayed(const Duration(seconds: 1));
      state = 1;
    });
    return 0;
  }
}

void test32Func(Ref ref) {
  final result = ref.watch(test32Provider);
  print('result: $result');
}
// 執行結果:
// result: 0
// 過 1 秒
// result: 1
// 過 2 秒
// result: 2
// 過 1 秒
// result: 1
```

### 9-2. 運算後過濾
`provider.select` 可以先拿到每一次變化，經由運算或是型別轉換後再進行一次比對，比對後參數不同才進行刷新

```dart=
@riverpod
class Test33 extends _$Test33 {
  @override
  int build() {
    Future(() async {
      while (true) {
        await Future.delayed(const Duration(seconds: 1));
        state++;
      }
    });
    return 0;
  }
}

void test33Func(Ref ref) {
  final result = ref.watch(
    test33Provider.select((value) {
      // 0, 1, 2 都為 false, 3 之後變為 true, 觸發刷新
      return value >= 3;
    }),
  );
  print('result: $result');
}
// 執行結果:
// result: false
// 過 3 秒
// result: true
```

### 9-3. select 使用時機

#### 9-3-1. 運算單純
Provider 若是運算單純，不一定要用 `provider.select` 功能，因為 Provider 的回傳本身就會再比對一次

```dart=
// 假設有 3 個參數為 Point(1, 3) -> Point(2, 2) -> Point(3, 1)
@riverpod
Point<int> test34(Ref ref) {...}

@riverpod
int test35(Ref ref) {
  // watch 內容刷新 3 次, 內容 3 個參數皆不同, test35 觸發 3 次刷新
  final point = ref.watch(test34Provider);
  // x + y 共運算 3 次
  return point.x + point.y;
}
// test35Provider 內容刷新 3 次, 共比對 3 次, 參數皆為 4
// ref.watch(test35Provider) 最終只觸發 1 次刷新

@riverpod
int test36(Ref ref) {
  // select x + y 共運算 3 次
  // watch 內容刷新 3 次, 共比對 3 次, 參數皆為 4, test36 只觸發 1 次刷新
  return ref.watch(test34Provider.select((point) => point.x + point.y));
}
// test36Provider 內容刷新 1 次, 共比對 1 次, 參數為 4
// ref.watch(test36Provider) 最終只觸發 1 次刷新
```

:::info
兩者並沒有減少運算次數，故使用 test35 相對簡單，雖然 test35Provider 會經歷較多次 update，但最終效能差不多
:::

#### 9-3-2. 過濾不必要參數
只需用到一個參數的一部分時，可以用 `provider.select` 過濾部分參數

```dart=
// 假設有 3 個參數為 Point(1, 2) -> Point(2, 2) -> Point(3, 2)
@riverpod
Point<int> test37(Ref ref) {...}

@riverpod
int test38(Ref ref) {
  // watch 內容刷新 3 次, 內容 3 個參數皆不同, test38 觸發 3 次刷新
  final point = ref.watch(test37Provider);
  // y + 2 共運算 3 次
  return point.y + 2;
}
// test38Provider 內容刷新 3 次, 共比對 3 次, 參數皆為 4
// ref.watch(test38Provider) 最終只觸發 1 次刷新

@riverpod
int test39(Ref ref) {
  // watch 內容刷新 3 次, 共比對 3 次, 參數皆為 2, test36 只觸發 1 次刷新
  final y = ref.watch(test37Provider.select((point) => point.y));
  // y + 2 共運算 1 次
  return y + 2;
}
// test39Provider 內容刷新 1 次, 共比對 1 次, 參數為 4
// ref.watch(test39Provider) 最終只觸發 1 次刷新
```

:::info
由於運算只需要 y，故僅監聽 y 可以避免 x 變化而觸發沒必要的刷新，有效降低運算次數
:::

#### 9-3-3. 運算複雜
可利用 `provider.select` 進行簡單運算後過濾，以減少後續複雜運算次數

```dart=
@riverpod
Point<int> test40(Ref ref) {...}

// 假設半徑是簡單運算
int calRadius(Point<int> point) {...}

// 假設面積是複雜運算
int calArea(int radius) {...}

@riverpod
int test41(Ref ref) {
  // 每次點發生變化, 都要經過簡單半徑以及複雜面積運算
  final point = ref.watch(test40Provider);
  final radius = calRadius(point);
  return calArea(radius);
}

@riverpod
int test42(Ref ref) {
  // 每次點發生變化, 先過濾不同的半徑
  final radius = ref.watch(
    test40Provider.select((point) => calRadius(point))
  );
  // 當半徑有變化, 再進行複雜面積運算
  return calArea(radius);
}
```

:::info
若 `point` 剛好點在相同半徑上移動，test42 只要進行一次複雜運算
:::

## 10. 組合監聽
Provider 可以監聽多個 Provider，遵守 `ref.read`、`ref.listen` 不會觸發刷新，只有 `ref.watch` 會觸發刷新

:::warning
Provider 之間不能循環監聽，例如 A watch B，B 又 watch A
:::

```dart=
@riverpod
int test43(Ref ref) => 1;

@riverpod
bool test44(Ref ref) => false;

@riverpod
String test45(Ref ref) => 'hello';

@riverpod
String test46(Ref ref) {
  // 若 test43Provider 發生變化, 不會觸發 test46Provider 刷新
  final value1 = ref.read(test43Provider);
  // 若 test44Provider 發生變化, 會觸發 test46Provider 刷新
  final value2 = ref.watch(test44Provider);
  // 若 test45Provider 發生變化, 會觸發 test46Provider 刷新
  final value3 = ref.watch(test45Provider);
  return 'value1: $value1, value2: $value2, value3: $value3';
}

void test46Func(Ref ref) {
  final result = ref.watch(test46Provider);
  print('result: $result');
}
// 執行結果:
// result: value1: 1, value2: false, value3: hello
```

高效刷新範例
===

## 範例 1
```dart=
@freezed
abstract class Message with _$Message {
  const factory Message(String content, double progress) = _Message;
}

// 6 筆資料
Stream<Message> get messageStream => Stream.fromIterable([
  Message('first', 1),
  Message('first', 1),
  Message('first', 2),
  Message('first', 2),
  Message('second', 2),
  Message('second', 2),
]);
```

:::danger
**BAD**
每次狀態更新，就算 Message 內容完全相同，都會刷新整個元件

```dart=
class MessageProgress extends StatefulWidget {
  const MessageProgress({super.key});

  @override
  State<MessageProgress> createState() => _MessageProgressState();
}

class _MessageProgressState extends State<MessageProgress> {
  Message? _message;

  @override
  void initState() {
    super.initState();
    // 於 initState 開始監聽 messageStream
    messageStream.listen((event) {
      setState(() {
        // 每次都觸發 build 刷新, 收到 6 次更新, 刷新 6 次
        _message = event;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    // Column 創建 7 次 (含 _message 未賦值 1 次)
    return Column(
      children: [
        // Text 創建 7 次
        Text('Message'),
        // Text 創建 7 次
        Text(_message?.content ?? ''),
        // LinearProgressIndicator 創建 7 次
        LinearProgressIndicator(value:_message?.progress ?? 0),
      ],
    );
  }
}
```
:::

:::danger
**BAD**
ref 為大範圍 ref，刷新範圍太大

```dart=
// AsyncLoading ->
// AsyncData(Message('first', 1)) ->
// AsyncData(Message('first', 2)) ->
// AsyncData(Message('second', 2))
@riverpod
Stream<Message> message(Ref ref) => messageStream;

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // ref 為此 MessageProgress 的 ref, 狀態變化會觸發此 build 刷新
    final message = ref.watch(messageProvider).value;
    // Column 創建 4 次
    return Column(
      children: [
        // Text 創建 4 次
        Text('Message'),
        // Text 創建 4 次
        Text(message?.content ?? ''),
        // LinearProgressIndicator 創建 4 次
        LinearProgressIndicator(value: message?.progress ?? 0),
      ],
    );
  }
}
```
:::

:::success
**GOOD**
ref 為小範圍 ref，只有相關資料有變化才會刷新相關元件

```dart=
// AsyncLoading ->
// AsyncData(Message('first', 1)) ->
// AsyncData(Message('first', 2)) ->
// AsyncData(Message('second', 2))
@riverpod
Stream<Message> message(Ref ref) => messageStream;

// '' -> 'first' -> 'second'
@riverpod
String messageContent(Ref ref) =>
    ref.watch(messageProvider).value?.content ?? '';

// 0 -> 1 -> 2
@riverpod
double messageProgress(Ref ref) =>
    ref.watch(messageProvider).value?.progress ?? 0;

class MessageProgress extends StatelessWidget {
  const MessageProgress({super.key});

  @override
  Widget build(BuildContext context) {
    // Column 創建 1 次
    return Column(
      children: [
        // Text 創建 1 次
        Text('Message'),
        Consumer(
          builder: (context, ref, child) {
            // ref 為此 Consumer 的 ref, 狀態變化會觸發此 builder 刷新
            // Text 創建 3 次
            final content = ref.watch(messageContentProvider);
            return Text(content);
          },
        ),
        Consumer(
          builder: (context, ref, child) {
            // ref 為此 Consumer 的 ref, 狀態變化會觸發此 builder 刷新
            // LinearProgressIndicator 創建 3 次
            final progress = ref.watch(messageProgressProvider);
            return LinearProgressIndicator(value: progress);
          },
        ),
      ],
    );
  }
}
```
:::

## 範例 2
```dart=
Stream<bool> get isConnectStream =>
    Stream.fromIterable([false, true, false]);

Stream<int> get randomIntStream async* {
  while (true) {
    await Future.delayed(const Duration(seconds: 1));
    yield Random().nextInt(100);
  }
}
```

:::danger
**BAD**
每次任何一個狀態更新，都會刷新整個元件

```dart=
class ConnectInt extends StatefulWidget {
  const ConnectInt({super.key});

  @override
  State<ConnectInt> createState() => _ConnectIntState();
}

class _ConnectIntState extends State<ConnectInt> {
  bool? _isConnect;
  int? _randomInt;

  @override
  void initState() {
    super.initState();
    // 於 initState 開始監聽 isConnectStream, randomIntStream
    isConnectStream.listen((event) {
      setState(() {
        // 每次都觸發 build 刷新
        _isConnect = event;
      });
    });
    randomIntStream.listen((event) {
      setState(() {
        // 每次都觸發 build 刷新
        _randomInt = event;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    if (_isConnect ?? false) {
      return Text('connect int: ${_randomInt ?? -1}');
    } else {
      // 未連線不使用 randomInt, 但 randomInt 還是會持續被刷新
      return Text('not connect');
    }
  }
}
```
:::

:::success
**GOOD**
Provider 只在必要的時候監聽

```dart=
@riverpod
Stream<bool> isConnect(Ref ref) => isConnectStream;

@riverpod
Stream<int> randomInt(Ref ref) => randomIntStream;

@riverpod
String connectIntMessage(Ref ref) {
  final isConnect = ref.watch(isConnectProvider);
  if (isConnect.value ?? false) {
    final randomInt = ref.watch(randomIntProvider);
    return 'connect int: ${randomInt.value ?? -1}';
  } else {
    // 未連線連不使用 randomIntProvider, provider 不會被創建
    return 'not connect';
  }
}

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final message = ref.watch(connectIntMessageProvider);
    return Text(message);
  }
}
```
:::
