# 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: 成果 ![image](https://hackmd.io/_uploads/SJu0lAZVR.png) ![image](https://hackmd.io/_uploads/rkn6gRbVC.png)