# flutter setup
## state management
[2.3.3 父Widget管理子Widget的状态](https://book.flutterchina.club/chapter2/state_manage.html#_2-3-3-%E7%88%B6widget%E7%AE%A1%E7%90%86%E5%AD%90widget%E7%9A%84%E7%8A%B6%E6%80%81) ,這是最基本的 declarative render 特性。
* [ Day 18 狀態管理 ](https://ithelp.ithome.com.tw/articles/10345140)
* [ Day 26 深入狀態管理 ](https://ithelp.ithome.com.tw/articles/10346305)
* [ reddit Flutter 狀態管理好難:到底要選哪個? ](https://www.reddit.com/r/FlutterDev/comments/1dwiu95/struggling_with_state_management_in_flutter_which/?tl=zh-hant)
* [ Day 29:Provider 狀態管理 ](https://ithelp.ithome.com.tw/articles/10227927)
* 圖形化了解什麼是 state management
* [7.2 数据共享(InheritedWidget)](https://book.flutterchina.club/chapter7/inherited_widget.html)
* `child: ShareDataWidget( //使用ShareDataWidget` 這行就表示接下來的 child 都在這個 context 底下。
---
* context
* InheritedWidget
* 內建
* provider style
* > provider 和 context 很近。
* **riverpod**
* [Riverpod 是什麼?它負責狀態管理嗎?跟著我了解幾個重要角色](https://ithelp.ithome.com.tw/articles/10332717)
* [Why Riverpod?](https://riverpod.dev/docs/introduction/why_riverpod)
* [Provider](https://pub.dev/packages/provider)
* 官方基礎元件
* 接近store
* [ChangeNotifier class](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html)
* 可以 mixin(with) or inherit (extends)
* event source
* > like redux style
* Bloc
* cubit
## folder/project structure
flutter 最適用的 UI 架構是 [MVVM](https://docs.flutter.dev/app-architecture/guide)。這是接下來的基本假設。
* [Architecture case study-Package structure](https://docs.flutter.dev/app-architecture/case-study#package-structure)
* 官方的,優先。
* 描述的很完整。
* [stacked](https://stacked.filledstacks.com/)
* 一個 framework
* MVVM 的更細節化,提供 template, API, 在 `provider` 上加狀態管理。
* [stacked_services](https://pub.dev/packages/stacked_services)
* [Flutter Daily 🍃: Leveling Up Your Project Structure 🆙](https://medium.com/@vortj/leveling-up-your-flutter-project-structure-fcb7099a3930)
* [Step-by-Step Guide to Flutter Folder Structure for Better Organization](https://softradixtechnology.hashnode.dev/step-by-step-guide-to-flutter-folder-structure-for-better-organization#heading-example-structure-for-a-huge-flutter-project)
* `Flutter Daily 🍃: Leveling Up Your Project Structure 🆙` 中 `layered` 版本的進階擴展。
* [Guide to app architecture](docs.flutter.dev/app-architecture/guide)
* 和 app arch 也有關係。但是要到專案很大才有差。
* [不專業教學系列 - Flutter 專案架構 開始前必備的知識!](https://hackmd.io/Nkdglr1rSdSXEfkNTmObbw)
* [ Day 8:Clean Architecture X Flutter(二)| Flutter 實踐篇 🛁 ](https://ithelp.ithome.com.tw/articles/10325942)
* `layred` and `Feature-Based`
* [Flutter — How to structure your next project](https://medium.com/@mirko.rapisarda/flutter-clean-architecture-719d01ccc11d)
* [Clean Architecture in Flutter | MVVM | BloC | Dio](https://medium.com/@yamen.abd98/clean-architecture-in-flutter-mvvm-bloc-dio-79b1615530e1)
以下是可能的術語轉換
* ui==presentation
* components == atoms == widgets
* screens
目前我建議先 follow layered
---
* 直接把 ideal 的做法寫成 linter pkg。
* LLM 非常需要 validator
* 目前已採用 官方 structure,所以更多的是去對照兩者的異同,吸收設計理念,而非真的採用。
* 希望官方 structure 也有這種工具
`flutter_lints` 好像是可以 define deps rule
---
* clean arch
* [flutter_clean_architecture ](https://pub.dev/packages/flutter_clean_architecture)
* > A Flutter package that makes it easy and intuitive to implement Uncle Bob's Clean Architecture in Flutter. This package provides basic classes that are tuned to work with Flutter and are designed according to the Clean Architecture.
* [clean_architecture_lint](https://pub.dev/packages/clean_architecture_lint)
* [create_project_aro](https://pub.dev/packages/create_project_aro)
* > This package generates a professional directory structure for Flutter projects with a focus on clean architecture and allows you to include a basic login example using the state manager of your choice (BLoC, Provider, or Riverpod).
* [clean_architecture_with_state_management](https://pub.dev/packages/clean_architecture_with_state_management)
* [arch_wizard](https://pub.dev/packages/arch_wizard)
* [流行的Flutter应用架构比较](https://blog.wodecun.com/blog/8321.html)
* [elementary](https://pub.dev/packages/elementary)
---
useful tool to fix structure
* [architecture_lintercopy](https://pub.dev/packages/architecture_linter)
## ui 單元測試
純單元測試就follow dart 就好。
* [6. widget 测试](https://codelabs.developers.google.com/codelabs/flutter-app-testing?hl=zh-cn#5)
* [5. 对提供程序进行单元测试](https://codelabs.developers.google.com/codelabs/flutter-app-testing?hl=zh-cn#4) 就是純dart logic unit test
* [Flutter 如何寫測試案例](https://lihsinplayer.medium.com/flutter-%E5%A6%82%E4%BD%95%E5%AF%AB%E6%B8%AC%E8%A9%A6%E6%A1%88%E4%BE%8B-5a41bad314fb)
###
## linter
* [dead_code_analyzer](https://pub.dev/packages/dead_code_analyzer)
* 現在是用 regex,AST 版還在開發中。
* [DCM-Check Unused Code](https://dcm.dev/docs/cli/code-quality-checks/unused-code/)
* 2023 就已經 close source。
*
## 整合測試
## ui layout
flutter ui layout 的模型與CSS box 模型幾乎一樣
* [Flutter doc](https://docs.flutter.dev/ui/layout)
* [css box觀念](https://www.w3schools.com/Css/css_boxmodel.asp)
flutter 官方有提供方便編輯ui樹狀結構的IDE
* [Dart DevTools](https://docs.flutter.dev/tools/devtools)
* [VS code使用>devtools就可以呼叫Widget Inspector](https://docs.flutter.dev/tools/devtools/vscode)
### RWD
缺乏 RWD 的跨平台,沒有實際意義。
* [Stop Using MediaQuery in Flutter](https://medium.com/@ashfaque-khokhar/stop-using-mediaquery-in-flutter-53f4dac48bb2)
* breakpoints 是一定要的。
* 另外 text scaling 更能大螢幕避免字太小的問題。
* [responsive_framework](https://pub.dev/packages/responsive_framework?source=post_page-----53f4dac48bb2---------------------------------------)
* `ResponsiveScaledBox`
## Flutter 鍵盤事件
使用KeyboardListener是一個方法,但需要觸發focus,例: 點擊按鈕或對話框
* [KeyboardListener class](https://api.flutter.dev/flutter/widgets/KeyboardListener-class.html)
* [GestureDetector來做focus管理(example)](https://api.flutter.dev/flutter/services/LogicalKeyboardKey-class.html#services.LogicalKeyboardKey.1)
* [焦點管理Fcous scope](https://wiki.ducafecat.tech/blog/translation/14-focusscope-in-flutter.html)
使用shortcut來定義快捷鍵,不需做額外動作即可autofocus,但沒有KeyUpEvent/KeyDownEvent、LongPress之類處理長按方法
* [shortcut應用例子](https://api.flutter.dev/flutter/widgets/Shortcuts-class.html)
## riverpod觀念
riverpod 是provider重新組合而成的單字,是一個狀態管理模組。
核心就是提供 provider 機制。但 provider 只是一個機制,要抽象成 viewModel 或 repository...,都是看個人的抽象模型去應用。
### Provider
提供widget間溝通狀態的橋樑。App狀態更新時,所有watch同一provider的widget會進行狀態更新。
[參考文件](https://riverpod.dev/docs/concepts2/providers)
[入門範例](https://riverpod.dev/docs/introduction/getting_started)
* Provider初始化問題
Provider並不會在App啟動時自動初始化,而是第一次被watch時才會被初始化。初始化後Provider所提供的數值才是正確的。
```
void main() {
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const _EagerInitialization(
// TODO: Render your app here
child: MaterialApp(),
);
}
}
class _EagerInitialization extends ConsumerWidget {
const _EagerInitialization({required this.child});
final Widget child;
@override
Widget build(BuildContext context, WidgetRef ref) {
// Eagerly initialize providers by watching them.
// By using "watch", the provider will stay alive and not be disposed.
ref.watch(myProvider);
return child;
}
}
```
[參考文件](https://riverpod.dev/docs/how_to/eager_initialization)
* 將參數傳入provider的方法
使用`.family` Modifier
The .family modifier is used to create a provider that takes an external parameter. This parameter is used to create a unique instance of the provider for each different value of the parameter.
1.Defining the Provider:
You use `.family` on your provider definition, and it takes an extra generic type for the parameter. The provider's function then takes an additional argument for that parameter.
```
final userProvider = FutureProvider.family<User, String>((ref, userId) async {
// Use the userId to fetch the user data
return await fetchUser(userId);
});
```
2.Reading the Provider
When you want to read or watch this provider from a widget or another provider, you pass the variable directly to it.
```
class UserProfile extends ConsumerWidget {
final String userId;
const UserProfile({Key? key, required this.userId}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsyncValue = ref.watch(userProvider(userId));
return userAsyncValue.when(
data: (user) => Text('User: ${user.name}'),
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
);
}
}
```
* Provider/NotifierProvider/StateProvider 的差別
Provider 是唯讀狀態,初始化後不可變更。
NotifierProvider 是可變的,被改變狀態後會重建任何聆聽它的widget。==狀態更新邏輯放在`Notifier`類別裡面,較強健且擴展性較強==
StateProvider是最簡單創造與變更狀態的方法,如`bool`, `int`, `String`, `enum`,用在簡單的UI狀態,==狀態更新邏輯放在widget裡面==。
* Provider
```
final apiServiceProvider = Provider((ref) => ApiService());
// In a widget, you can read it like this:
final apiService = ref.read(apiServiceProvider);
```
* NotifierProvider
```
// The class that holds your state and logic
class Counter extends Notifier<int> {
@override
int build() {
return 0;
}
void increment() {
state++;
}
}
// The provider that exposes the Notifier
final counterProvider = NotifierProvider<Counter, int>(Counter.new);
// In a widget, you can watch the state and call methods on the notifier:
final count = ref.watch(counterProvider);
ref.read(counterProvider.notifier).increment();
```
* StateProvider
```
final counterProvider = StateProvider<int>((ref) => 0);
// To read the value
final count = ref.watch(counterProvider);
// To increment the value
ref.read(counterProvider.notifier).state++;
```
* `ref.watch(provider)` 與 `ref.watch(provider.future)` 的區別
`ref.watch(provider)` 是回傳非同步值,有三個狀態(data, loading, error)。
`ref.watch(provider.future)` 回傳`Future<T>`數值本身。
```
final userProvider = FutureProvider<User>((ref) async {
// Simulates a network request
await Future.delayed(const Duration(seconds: 2));
return User(name: 'John Doe');
});
class UserProfile extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsyncValue = ref.watch(userProvider);
return userAsyncValue.when(
data: (user) => Text('User: ${user.name}'),
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
);
}
}
```
```
final userIdProvider = Provider<String>((ref) => 'user-123');
final userProfileProvider = FutureProvider<Map<String, dynamic>>((ref) async {
// We need to wait for the userId to be available before fetching the profile.
// Using .future allows us to await the result directly.
final userId = await ref.watch(userIdProvider.future);
// Now we can use the userId to fetch the user profile.
return fetchUserProfile(userId);
});
```
### Consumer
所有接收Provider狀態的widget都必須繼承Consumer,ConsumerStatefulWidge就是在flutter的StatefulWidge上再包裝一層Consumer
[參考文件](https://riverpod.dev/docs/concepts2/consumers)
### Ref
讓Provider間互相作用、傳遞狀態給Consumer的代表參數。
```
Consumer(
builder: (context, ref, _) {
// ❌ Don't use "read" as a mean to ignore changes
final tick = ref.read(tickProvider);
// ✅ Use "watch" to listen to changes.
// This shouldn't be a bottle-neck in your apps. Do not over-optimize.
final tick = ref.watch(tickProvider);
// ✅ Use "select" to only listen to the specific part of the state you care about
final isEven = ref.watch(
tickProvider.select((tick) => tick.isEven),
);
...
},
);
```
[參考文件](https://riverpod.dev/docs/concepts2/refs)
### ref.watch vs ref.read vs ref.listen
ref.watch: 用於在widget建構時==訂閱==狀態變化,當狀態變化時會觸發widget重建。==注意:用於讀檔動作時需要多加小心,應避免狀態更新時因訂閱導致不小心讀到舊檔的情況==
ref.read: 用於一次性讀取當前狀態值,不會訂閱狀態變化。
ref.listen: 用於監聽狀態變化並執行副作用(如顯示SnackBar),但不會觸發widget重建。
## framework
* Serverpod
* full-end framework
* Shelf
* express.js in dart
### login service