昨天我們提到了狀態管理的基本功:InheritedWidget
今天談談進階版的InheritedWidget:Prodiver
Provider是一個套件
不只是Flutter Favorite而已
更是Flutter 12125 個套件裡面
最多人喜歡的(唯一超過兩千個like)
接下來我們就趕快來看看Prodiver該如何使用吧

通常我們以前可能會用NotificationCenter來完成上述情境☘️☘️☘️
換成Provider會像這樣子
notifyListeners()去通知大家class LoginChangeNotifier with ChangeNotifier { 
  //Flutter沒有private修飾子, 用下底線開頭表示私有
  bool _isLogin = false;
  bool get isLogin => _isLogin;
  
  //因為Provider好像沒有提供onChange之類的callback, 所以自行定義
  VoidCallback onLogin;
  VoidCallback onLogout;
  loginToggle() {
    _isLogin = !_isLogin;
    notifyListeners();
    if (isLogin) {
      onLogin();
    } else {
      onLogout();
    }
  }
}
return MultiProvider(
  child: tabScaffold,
  providers: [
    ChangeNotifierProvider.value(
      value: LoginChangeNotifier()
    )
  ],
);
listen參數/// If [listen] is
true(default), later value changes will trigger a new
/// [State.build] to widgets, and [State.didChangeDependencies] for
/// [StatefulWidget].
我的理解是當你發動時
狀態已經自己管理了
就不需要接受通知
故為false
Switch(
  value: isLogin,
  onChanged: (isOn) {
    setState(() {
      isLogin = isOn;
    });
    Provider.of<LoginChangeNotifier>(context, listen: false).loginToggle();
  },
)
A. 一樣使用Provider.of(這時就要listen)
    Provider.of<LoginChangeNotifier>(context).onLogout = (){
      Navigator.pop(context);
    };
B. 使用Consumer類別
Consumer<LoginChangeNotifier>( 
  child: Text("如果登出就會踢回前一頁"),
  builder: (context, LoginChangeNotifier loginModel, widget){
    loginModel.onLogout = () {
      Navigator.pop(context);
    };
    //這邊return出去的才是最後顯示的widget
    return Row(children: [
      widget, //這個widget就是上面的child
      Text(" :)")
    ], mainAxisAlignment: MainAxisAlignment.center);
  },
)
有什麼差別呢?
根據等等文末推薦的資料
首先这证明了 Provider.of(context) 会导致调用的 context 页面范围的刷新。
那么第二个页面刷新没有呢? 刷新了,但是只刷新了 Consumer 的部分,甚至连浮动按钮中的 Icon 的不刷新我们都给控制了。
你可以在 Consumer 的 builder 方法中验证,这里不再啰嗦
假如你在你的应用的 页面级别 的 Widget 中,使用了 Provider.of(context)。会导致什么后果已经显而易见了,每当其状态改变的时候,你都会重新刷新整个页面。
虽然你有 Flutter 的自动优化算法给你撑腰,但你肯定无法获得最好的性能。
所以在这里我建议各位尽量使用 Consumer 而不是 Provider.of(context) 获取顶层数据。
簡言之就是Consumer只刷新局部Provider.of刷新整頁
還記得前面提到的胡椒跟鹽巴嗎?
在code裡面
我就不特別解說了
(那邊也是花了點時間研究...)
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:icofont_flutter/icofont_flutter.dart';
import 'package:provider/provider.dart';
class LessonPageProvider extends StatefulWidget {
  @override
  _LessonPageProviderState createState() => _LessonPageProviderState();
}
class _LessonPageProviderState extends State<LessonPageProvider> {
  bool isLogin = false;
  int currentIndex = 0;
  final pages = [
    PushNextPage(Colors.orangeAccent, showAppBar: true),
    PushNextPage(Colors.black12),
    PushNextPage(Colors.brown),
  ];
  final items = [
    BottomNavigationBarItem(icon: Icon(IcoFontIcons.brandMacOs), label: "甲"),
    BottomNavigationBarItem(icon: Icon(IcoFontIcons.brandMacOs), label: "乙"),
    BottomNavigationBarItem(icon: Icon(IcoFontIcons.brandMacOs), label: "丙"),
  ];
  final keys = [
    GlobalKey<NavigatorState>(),
    GlobalKey<NavigatorState>(),
    GlobalKey<NavigatorState>()
  ];
  @override
  Widget build(BuildContext context) {
    final tabScaffold = CupertinoTabScaffold(
      tabBar: CupertinoTabBar(
        items: items,
        onTap: (idx){
          if (idx == currentIndex) {
            keys[currentIndex].currentState.popUntil((route) => route.isFirst);
          }
          currentIndex = idx;
        },
      ),
      tabBuilder: (ctx, idx){
        return CupertinoTabView(
          navigatorKey: keys[idx],
          builder: (BuildContext context) =>
            CupertinoPageScaffold(
              child: pages[idx],
              navigationBar: idx != 1 ? null : CupertinoNavigationBar(
                middle: Text("庫比蒂諾"),
              ),
            ),
        );
      },
    );
    return MultiProvider(
      child: tabScaffold,
      providers: [
        ChangeNotifierProvider.value(
          value: LoginChangeNotifier()
        )
      ],
    );
  }
}
//-
class PushNextPage extends StatefulWidget {
  Color backgroundColor = Colors.white;
  bool showAppBar;// = false; //這邊給預設值沒用...要寫在建構子
  PushNextPage(this.backgroundColor, {this.showAppBar = false});
  @override
  _PushNextPageState createState() => _PushNextPageState();
}
class _PushNextPageState extends State<PushNextPage> {
  bool isLogin = false;
  final scaffoldKey = GlobalKey<ScaffoldState>();
  @override
  Widget build(BuildContext context) {
    Widget nextPage = PopPreviousPage();
    final center = Container(
      color: widget.backgroundColor,
      alignment: Alignment.center,
      child: CupertinoButton(
        child: Icon(Icons.next_week, size: 30),
        onPressed: (){
          if (Provider.of<LoginChangeNotifier>(context).isLogin) {
            Navigator.push(context,
                MaterialPageRoute(builder: (context) => nextPage)
            );
          } else {
            scaffoldKey.currentState.showSnackBar(
              SnackBar(
                content: Text("只有登入後才可進入下一頁喔 :)")
              )
            );
          }
        },
      )
    );
    final drawer = Drawer(
      child: Column(
        children: [
          Container(
            padding: EdgeInsets.only(top: 30, bottom: 16),
            child: Icon(IcoFontIcons.waiterAlt, size: 100),
          ),
          ListTile(
            title: Text(isLogin ? "登出" : "登入",
              textAlign: TextAlign.center,
              style: TextStyle(
                  fontSize: 20
              ),
            ),
            trailing: Switch(
              value: isLogin,
              onChanged: (isOn) {
                //這句放在setState前面就會壞掉, 也太奇怪了吧....
                //Provider.of<LoginChangeNotifier>(context, listen: false).loginToggle();
                setState(() {
                  isLogin = isOn;
                });
                Provider.of<LoginChangeNotifier>(context, listen: false).loginToggle();
              },
            )
          )
        ],
      )
    );
    return Scaffold(
      key: scaffoldKey,
      appBar: widget.showAppBar ? AppBar(title: Text("瑪提利尤")) : null,
      drawer: widget.showAppBar ? SizedBox(width: 200, child: drawer) : null,
      body: center,
    );
  }
}
//-
class PopPreviousPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        alignment: Alignment.center,
        child: Consumer<LoginChangeNotifier>( //用Consumer只刷新這邊
          child: Text("如果登出就會踢回前一頁"),
          builder: (context, LoginChangeNotifier loginModel, widget){
            loginModel.onLogout = () {
              Navigator.pop(context);
            };
            //這邊return出去的才是最後顯示的widget
            return Row(children: [
              widget, //這個widget就是上面的child, child可以保持不重刷
              Text(" :)")
            ], mainAxisAlignment: MainAxisAlignment.center);
          },
        )
      )
    );
  }
}
//-
class LoginChangeNotifier with ChangeNotifier {
  bool _isLogin = false;
  bool get isLogin => _isLogin;
  VoidCallback onLogin;
  VoidCallback onLogout;
  loginToggle() {
    _isLogin = !_isLogin;
    notifyListeners();
    if (isLogin) {
      onLogin();
    } else {
      onLogout();
    }
  }
}
最後推薦一個超詳細的Provider大全:Flutter | 状态管理指南篇——Provider
講得很仔細
內容又超多(目錄都快比我今天的文章還多了 不誇張)
推薦給想深入Provider的朋友
下集預告:Notification
