# flutter Hook + Riverpod + Future筆記
## :mag_right: 模擬 call API
**:hammer: controller**
```dart
//注入依賴
//可以讓API當下的數值同時套用到兩個數據模組當中(riverpod)
//簡單概念就是 => 將Riverpod-provider賦予給HookFutureService()進行值的操作
//就像是provider的角度來說,正常能夠當下進行數值更新那就會是需要當下的context
//那注入依賴就像是給予context(ref)的概念
final apiServiceProvider = Provider((ref) => HookFutureService());
//第一個數據模型的宣告
//使用到StateNotifierProvider
//泛型為<目標數據模組 , 目前格式>
//Return內容為獲取api當中的資料所以會寫成 "目標數據模型(ref.read(賦予依賴的api caller))"
//這邊的概念可以想成
//因為我在ApiNotifier裡面想要拿到能夠call api的方法(hookFutureService.fetchData()),所以我回傳值就應該是ApiNotifier(相同ref的api方法)
final apiNotifierProvider =
StateNotifierProvider<ApiNotifier, AsyncValue<String>>((ref) {
return ApiNotifier(ref.read(apiServiceProvider));
});
//同上
final otherApiNotifierProvider =
StateNotifierProvider<OtherApiNotifier, AsyncValue<String>>((ref) {
return OtherApiNotifier(ref.read(apiServiceProvider));
});
//宣告數據模型
//繼承會需要繼承的類型
class ApiNotifier extends StateNotifier<AsyncValue<String>> {
//宣告API class可以被調用
final HookFutureService hookFutureService;
//依賴注入
//因為在apiNotifierProvider、apiServiceProvider所依賴的HookFutureService與此方法裏頭的HookFutureService是不同的依賴
//因此在這邊的ApiNotifier(this.hookFutureService)是屬於一種辨別他是誰的概念
//=> 因為上面的API方法跟下面的都是獨立的,所以要辨別誰是誰的各自辨別方法
ApiNotifier(this.hookFutureService) : super(const AsyncValue.data(''));
Future<void> fetchData() async {
//防止二次呼叫
if (state is AsyncLoading) return;
//設定狀態為Loading
state = const AsyncValue.loading();
devLog('Fetching data...', 'Fetching data...'); // 日誌打印
try {
//API 呼叫
final data = await hookFutureService.fetchData();
devLog('Data fetched:', data); // 日誌打印
//呼叫完成將值傳出
state = AsyncValue.data(data);
} catch (e, stack) {
state = AsyncValue.error(e, stack);
}
}
}
class OtherApiNotifier extends StateNotifier<AsyncValue<String>> {
final HookFutureService hookFutureService;
OtherApiNotifier(this.hookFutureService) : super(const AsyncValue.data(''));
Future<void> fetchOtherData() async {
if (state is AsyncLoading) return;
state = const AsyncValue.loading();
devLog('Fetching data...', 'Fetching data...'); // 日誌打印
try {
final data = await hookFutureService.fetchOtherData();
devLog('Data fetched:', data); // 日誌打印
state = AsyncValue.data(data);
} catch (e, stack) {
state = AsyncValue.error(e, stack);
}
}
}
```
**:hammer: repository**
```dart
class HookFutureService {
Future<String> fetchData() async {
await Future.delayed(const Duration(seconds: 2));
return 'Fetched data from API';
}
Future<String> fetchOtherData() async {
await Future.delayed(const Duration(seconds: 2));
return 'Fetched other data from API';
}
}
```
**:hammer: UI view**
```dart
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:widget_test/helper/devlog.dart';
import 'package:widget_test/model/riverPod_Data.dart';
class FectchDataPage extends HookConsumerWidget {
const FectchDataPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final apiState = ref.watch(apiNotifierProvider);
final otherApiState = ref.watch(otherApiNotifierProvider);
final nameTest = useState('JIM派');
//在Hook的世界,只要有用到它們相關的方法就是會是動態Widget,
//這些方法都會是等同於initState這種的一進到頁面會做的事情
useEffect(
() {
// 錯誤 [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Tried to modify a provider while the widget tree was building.
// *遇到的問題是因為在UI渲染都還沒開始的時候就進行了UI的變動,所以可能會有錯誤
// 結果辦法就是=>等到UI渲染完再進行動作
//或是 WidgetsBinding.instance.addPostFrameCallback((){})
Future.microtask(() {
devLog('useEffect()', '正在嘗試獲取資料'); // 日誌打印
ref.read(apiNotifierProvider.notifier).fetchData();
ref.read(otherApiNotifierProvider.notifier).fetchOtherData();
});
return null;
},
[], // 空的依賴列表,表示只在組件首次渲染時執行
);
Widget baseWidget(Widget body) {
return Scaffold(
appBar: AppBar(
title: const Text('Hook_Future_Page'),
),
body: body,
);
}
return baseWidget(
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
apiState.when(
loading: () => const CircularProgressIndicator(),
error: ((error, stackTrace) => Text('Error: $error')),
data: (data) => Text(data),
),
const SizedBox(height: 20),
otherApiState.when(
loading: () => const CircularProgressIndicator(),
error: ((error, stackTrace) => Text('Error: $error')),
data: (data) => Text(data),
),
const SizedBox(height: 20),
otherApiState.when(
loading: () => const CircularProgressIndicator(),
error: ((error, stackTrace) => Text('Error: $error')),
data: (data) => Text(nameTest.value),
),
],
),
),
);
}
}
```
## :package: 成果

