Slide 1

Slide 1 text

Flutter Provider 看似簡單卻⼜不簡單的 狀態管理⼯具 Kaohsiung Johnny Sung

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

Full stack developer Johnny Sung (宋岡諺) https://fb.com/j796160836 https://blog.jks.co ff ee/ https://www.slideshare.net/j796160836 https://github.com/j796160836

Slide 4

Slide 4 text

https://pub.dev/packages/provider

Slide 5

Slide 5 text

講在 Provider 之前 — InheritedWidget Kaohsiung

Slide 6

Slide 6 text

https://www.youtube.com/watch?v=Zbm3hjPjQMk

Slide 7

Slide 7 text

在 Flutter 的世界裡...

Slide 8

Slide 8 text

萬物皆 Widget 在對應的狀態產⽣對應的畫⾯

Slide 9

Slide 9 text

https://abhishekdoshi26.medium.com/deep-dive-into- fl utter-trees-542f7395df5c

Slide 10

Slide 10 text

Widget 兄弟其實還少⼀個 • StatefulWidget • StatelessWidget • InheritedWidget 就是我啦!

Slide 11

Slide 11 text

InheritedWidget 想解決 
 Widget tree 跨層傳遞資料 不便的問題

Slide 12

Slide 12 text

https://docs. fl utter.dev/data-and-backend/state-mgmt/simple

Slide 13

Slide 13 text

class FrogColor extends InheritedWidget { const FrogColor({ super.key, required this.color, required super.child, }); final Color color; static FrogColor? maybeOf(BuildContext context) { return context.dependOnInheritedWidgetOfExactType(); } static FrogColor of(BuildContext context) { final FrogColor? result = maybeOf(context); assert(result != null, 'No FrogColor found in context'); return result!; } @override bool updateShouldNotify(FrogColor oldWidget) => color != oldWidget.color; } https://api. fl utter.dev/ fl utter/widgets/InheritedWidget-class.html 背後宣告

Slide 14

Slide 14 text

// continuing from previous example... class MyPage extends StatelessWidget { const MyPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: FrogColor( color: Colors.green, child: Builder( builder: (BuildContext innerContext) { return Text( 'Hello Frog', style: TextStyle(color: FrogColor.of(innerContext).color), ); }, ), ), ); } } https://api. fl utter.dev/ fl utter/widgets/InheritedWidget-class.html 呼叫使⽤

Slide 15

Slide 15 text

Provider 就是 InheritedWidget 的延伸(與包裝) 其實

Slide 16

Slide 16 text

不信?讓我們看看⼀⼩段 Provider 的原始碼

Slide 17

Slide 17 text

class FrogColor extends InheritedWidget { const FrogColor({ super.key, required this.color, required super.child, }); final Color color; static FrogColor? maybeOf(BuildContext context) { return context.dependOnInheritedWidgetOfExactType(); } static FrogColor of(BuildContext context) { final FrogColor? result = maybeOf(context); assert(result != null, 'No FrogColor found in context'); return result!; } @override bool updateShouldNotify(FrogColor oldWidget) => color != oldWidget.color; } https://api. fl utter.dev/ fl utter/widgets/InheritedWidget-class.html (剛剛看到的段落)

Slide 18

Slide 18 text

class Provider extends InheritedProvider { Provider({ Key? key, required Create create, Dispose? dispose, bool? lazy, TransitionBuilder? builder, Widget? child, }) : super( key: key, lazy: lazy, builder: builder, create: create, dispose: dispose, debugCheckInvalidValueType: kReleaseMode ? null : (T value) => Provider.debugCheckInvalidValueType?.call(value), child: child, ); static T of(BuildContext context, {bool listen = true}) { assert( context.owner!.debugBuilding || listen == false || debugIsInInheritedProviderUpdate, ''' Tried to listen to a value exposed with provider, from outside of the widget tree. … 略 ''', ); final inheritedElement = _inheritedElementOf(context); if (listen) { context.dependOnInheritedWidgetOfExactType<_InheritedProviderScope>(); } final value = inheritedElement?.value; if (_isSoundMode) { if (value is! T) { throw ProviderNullException(T, context.widget.runtimeType); } return value; } return value as T; } } Provider 的原始碼

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

Provider 家族 • Provider:提供遠端存取的服務 • ChangeNoti fi erProvider:它會監聽 model 的變化。當資料發⽣變更,它會重繪 Consumer 的⼦ widget。 • FutureProvider:是 FutureBuilder 的⼀個封裝,提供⼀個 Future,FutureProvider 會在 Future 完成的時候 通知 Consumer 重繪它的 Widget ⼦樹。 • StreamProvider:是 StreamBuilder 的⼀個封裝,提供⼀個 Stream,然後 Consumer 會在 Stream 收到事 件時更新它的 Widget⼦樹。

Slide 21

Slide 21 text

import 'package:flutter/foundation.dart'; class Counter with ChangeNotifier { int _count = 0; int get count => _count; void increment() { _count++; notifyListeners(); } }

Slide 22

Slide 22 text

import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'counter.dart'; class MyHomePage extends StatefulWidget { const MyHomePage({super.key}); @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { @override Widget build(BuildContext context) { return Scaffold( body: ChangeNotifierProvider( create: (context) => Counter(), child: Builder(builder: (context) { final counter = context.watch().count; return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('$counter'), ElevatedButton( onPressed: () { final counter = context.read(); counter?.increment(); print('count=${counter?.count ?? 0}'); }, child: const Text('Push me')), ], ), ); }), ), ); } }

Slide 23

Slide 23 text

Consumer(builder: (context, myModel, child) { return Text('${myModel.count}'); }), 或 三種寫法都可以 註:Consumer 的部份 Text('${context.watch().count}') Text('${Provider.of(context).count}') 或

Slide 24

Slide 24 text

/// Exposes the [read] method. extension ReadContext on BuildContext { T read() { return Provider.of(this, listen: false); } } Provider 原始碼中 context.read() 對應到 Provider.of()

Slide 25

Slide 25 text

選單按鈕 分⾴內⾴

Slide 26

Slide 26 text

import 'package:flutter/foundation.dart'; class TabProvider with ChangeNotifier { int _currentIndex = 0; int get currentIndex => _currentIndex; set currentIndex(int index) { _currentIndex = index; notifyListeners(); } }

Slide 27

Slide 27 text

mixin & with Mixins are a way of reusing a class’s code in multiple class hierarchies. 簡單來說就是: 
 我提供⼀些⽅法給你使⽤,但我不⽤成為你的⽗類別。 mixin ShowName { void showName() { print('mixin ShowName'); } } class Quadrilateral with ShowName { void showAllSideLength() { showName(); } } https://ithelp.ithome.com.tw/articles/10268368?sc=iThelpR

Slide 28

Slide 28 text

import 'package:flutter/material.dart'; import 'package:flutter_provider_tab_demo/tab_provider.dart'; import 'package:flutter_provider_tab_demo/tab_screen.dart'; import 'package:provider/provider.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MultiProvider( providers: [ ChangeNotifierProvider(create: (context) => TabProvider()), ], child: TabScreen(), ), ); } }

Slide 29

Slide 29 text

import 'package:flutter/material.dart'; import 'package:flutter_provider_tab_demo/tab_provider.dart'; import 'package:provider/provider.dart'; class TabItem { final String name; final int index; TabItem(this.name, this.index); } class TabScreen extends StatelessWidget { final List tabItems = [ TabItem('Tab 1', 0), TabItem('Tab 2', 1), TabItem('Tab 3', 2), ]; TabScreen({super.key}); @override Widget build(BuildContext context) { // build } } return Scaffold( appBar: AppBar( title: const Text('Tab Example'), ), body: Column( children: [ ListView.builder( shrinkWrap: true, itemCount: tabItems.length, itemBuilder: (context, index) { TabItem tabItem = tabItems[index]; return ListTile( title: Text(tabItem.name), onTap: () { TabProvider p = context.read(); p.currentIndex = tabItem.index; }, ); }, ), Expanded( child: IndexedStack( index: context.watch().currentIndex, children: const [ Center(child: Text('Tab 1')), Center(child: Text('Tab 2')), Center(child: Text('Tab 3')), ], ), ), ], ), ); 選單按鈕 分⾴內⾴

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

Provider 使⽤情境 • 會員帳號權限管理 • 帳號登入 • Token 過期、被強迫登出 • Widget tree 平⾏傳遞資料 (例如:分⾴切換)

Slide 32

Slide 32 text

全域的狀態機變數 Provider 我的理解:

Slide 33

Slide 33 text

全域變數 雖可恥但有⽤

Slide 34

Slide 34 text

https://stackover fl ow.com/questions/73226745/ fl utter-many-bloc-providers-cluttering-widget-tree

Slide 35

Slide 35 text

MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: BlocProvider( create: (context) => SigninCubit(), child: BlocProvider( create: (context) => UpdateCubit(), child: BlocProvider( create: (context) => ProfileBloc(), child: BlocProvider( create: (context) => VoteBloc(), child: BlocProvider( create: (context) => MessageBloc(), child: BlocProvider( create: (context) => LeaderboardBloc(), child: BlocProvider( create: (context) => CommentBloc(), child: BlocProvider( create: (context) => EditbioCubit(), child: BlocProvider( create: (context) => CommentCubit(), child: BlocProvider( create: (context) => SearchBloc(), child: BlocProvider( create: (context) => SwipeBloc(), child: Scaffold( body: Builder(builder: (context) { return const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('Sample'), ], ), ); }), ), ), ), ), ), ), ), ), ), ), ), ), ); 不免俗的,Flutter 沒寫好的話,很容易寫出⼀堆波動拳 https://captown.capcom.com/zh-TW/museums/street fi ghter/5/images/125#group=0&photo=0

Slide 36

Slide 36 text

部分的波動拳可以⽤ MultiXXXProvider 解決部分的問題 但這範例還有權責不明確問題 MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MultiBlocProvider( providers: [ BlocProvider(create: (context) => SigninCubit()), BlocProvider(create: (context) => UpdateCubit()), BlocProvider(create: (context) => ProfileBloc()), BlocProvider(create: (context) => VoteBloc()), BlocProvider(create: (context) => MessageBloc()), BlocProvider(create: (context) => LeaderboardBloc()), BlocProvider(create: (context) => CommentBloc()), BlocProvider(create: (context) => EditbioCubit()), BlocProvider(create: (context) => CommentCubit()), BlocProvider(create: (context) => SearchBloc()), BlocProvider(create: (context) => SwipeBloc()), ], child: Scaffold( body: Builder(builder: (context) { return const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('Sample'), ], ), ); }), ), ), );

Slide 37

Slide 37 text

要慎⽤呀! https://pic1.zhimg.com/80/v2-5af8d68f267c1ed3ece59d5bd919a870_1440w.webp

Slide 38

Slide 38 text

解決波動拳⼩建議 • View 分類好封裝進 StatelessWidget • 邏輯與 View 分類分開存放 • ⽤ MultiProvider 或 MultiBlocProvider 等類似套件來拉平

Slide 39

Slide 39 text

Q: Provider 與 Bloc 有什麼差異? 我的回答:要解決的問題不同,使⽤不同的解決⽅式

Slide 40

Slide 40 text

當 Bloc 遇上 provider ? http://teddy-chen-tw.blogspot.com/2012/03/blog-post_20.html 爭什麼,摻在⼀起做成撒尿⽜丸啊

Slide 41

Slide 41 text

https://pub.dev/packages/ fl utter_bloc

Slide 42

Slide 42 text

https://pub.dev/packages/ fl utter_bloc 😜

Slide 43

Slide 43 text

BlocProvider = Bloc + Provider

Slide 44

Slide 44 text

BlocProvider 家族 • BlocProvider • BlocBuilder • BlocListener • BlocConsumer = BlocBuilder + BlocListener

Slide 45

Slide 45 text

BlocProvider( create: (BuildContext context) => LoginBloc()), child: …, ), 在 Widget Tree 最頂層處使⽤ BlocProvider 並宣告⾃訂的 Bloc (以下是⼀些 pseudo code) BlocProvider 使⽤⽅式 (1/4)

Slide 46

Slide 46 text

BlocBuilder(builder: (context, state) { if (state is LoginStateLoggedIn) { return …; } if (state is LoginStateInitial) { return …; } return Container(); } 在所需 Widget 的 build() 處使⽤ BlocBuilder (以下是⼀些 pseudo code) BlocProvider 使⽤⽅式 (2/4) 這裡沒指定 bloc 參數時,預設會 Provider 往上找

Slide 47

Slide 47 text

在需要聆聽事件的地⽅使⽤ BlocListener (以下是⼀些 pseudo code) BlocProvider 使⽤⽅式 (3/4) 這裡沒指定 bloc 參數時,預設會 Provider 往上找 BlocListener( listener: (context, state) { if (state is LoginStateError) { // Do Something } if (state is LoginStateLoggedIn) { // Do Something } }, child: … );

Slide 48

Slide 48 text

在需要變更狀態的地⽅使⽤ context.read() 並操作 Bloc (以下是⼀些 pseudo code) BlocProvider 使⽤⽅式 (4/4) ElevatedButton( child: const Text('Login'), onPressed: () { context.read().add(LoginEventLogin()); }, )

Slide 49

Slide 49 text

https://docs. fl utter.dev/data-and-backend/state-mgmt/simple

Slide 50

Slide 50 text

Q&A

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

其他狀態管理比較 Kaohsiung

Slide 53

Slide 53 text

https://k.sina.cn/article_7407912364_1b98bc5ac00101jqaw.html?from=cul

Slide 54

Slide 54 text

https://pub.dev/packages/mobx

Slide 55

Slide 55 text

No content

Slide 56

Slide 56 text

mobx_codegen

Slide 57

Slide 57 text

No content

Slide 58

Slide 58 text

https://pub.dev/packages/scoped_model

Slide 59

Slide 59 text

No content

Slide 60

Slide 60 text

https://pub.dev/packages/get

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

https://pub.dev/packages/riverpod

Slide 64

Slide 64 text

No content

Slide 65

Slide 65 text

感謝聆聽