Flutter - Provider,我们应该学会的状态管理

初步接触

刚开始接触Provider的时候,感觉比较懵逼,因为之前连RN的状态管理Redux都没搞明白,主要是觉得太繁琐了吧。在使用Provider之前,看过一些BLOC,ScopeModel等框架,但是感觉起来还是Provider更好用吧。Provider也是谷歌官方所推荐的。

学习Provider,网上看到最多的是Counter计数器。这里我们不讲计数器,我们谈一个项目中比较实际一点的的LoaderContainer (加载器)。

首先,LoaderContainer是我编写的一个加载器类

  • LoaderContainer 加载器组件,声明了加载器状态值,加载器在不同状态下呈现的视图。
import 'package:flutter/material.dart';
import 'package:project_name/core/widget/rounded_button.dart';
import 'package:project_name/data/app/app_constants.dart';

enum LoaderState { NoAction, Loading, Succeed, Failed, NoData }

class LoaderContainer extends StatefulWidget {
  LoaderContainer({
    Key key,
    @required this.contentView,
    this.loadingView,
    this.errorView,
    this.emptyView,
    @required this.loaderState,
    this.onReload,
  }) : super(key: key);

  final LoaderState loaderState;
  final Widget loadingView;
  final Widget errorView;
  final Widget emptyView;
  final Widget contentView;
  final Function onReload;

  @override
  State createState() => _LoaderContainerState();
}

class _LoaderContainerState extends State<LoaderContainer> {
  @override
  Widget build(BuildContext context) {
    Widget currentWidget;
    switch (widget.loaderState) {
      case LoaderState.Loading:
        currentWidget = widget.loadingView ?? _ClassicalLoadingView();
        break;
      case LoaderState.Failed:
        currentWidget = widget.errorView ??
            _ClassicalErrorView(
              onReload: () => widget.onReload(),
            );
        break;
      case LoaderState.NoData:
        currentWidget = widget.emptyView ?? _ClassicalNoDataView();
        break;
      case LoaderState.Succeed:
      case LoaderState.NoAction:
        currentWidget = widget.contentView;
        break;
    }
    return currentWidget;
  }
}

class _ClassicalLoadingView extends StatelessWidget {
  @override
  Widget build(BuildContext context) => Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            CircularProgressIndicator(
              valueColor: AlwaysStoppedAnimation(AppConstants.themeColor),
            ),
            Padding(
              padding: const EdgeInsets.only(top: 15),
              child: Text(
                '正在拼命加载中...',
                style: TextStyle(
                  fontSize: 13,
                  color: Color(0xff999999),
                ),
              ),
            ),
          ],
        ),
      );
}

class _ClassicalErrorView extends StatelessWidget {
  _ClassicalErrorView({@required this.onReload}) : super();

  final Function onReload;

  @override
  Widget build(BuildContext context) => Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Image.asset(
              'asset/img/ic_default_load_data_failed.png',
              width: 80,
              height: 80,
              color: Color(0xff999999),
            ),
            Padding(
              padding: const EdgeInsets.only(top: 12),
              child: Text(
                '加载失败,请稍后点击重试',
                style: TextStyle(
                  fontSize: 13,
                  color: Color(0xff999999),
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.only(top: 20),
              child: RoundedButton(
                bgColor: AppConstants.themeColor,
                onPressed: onReload,
                child: Padding(
                  padding:
                      const EdgeInsets.symmetric(vertical: 5, horizontal: 20),
                  child: Text(
                    '重新加载',
                    style: TextStyle(color: Theme.of(context).buttonColor),
                  ),
                ),
              ),
            ),
          ],
        ),
      );
}

class _ClassicalNoDataView extends StatelessWidget {
  @override
  Widget build(BuildContext context) => Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Image.asset(
              'asset/img/ic_default_load_data_failed.png',
              width: 80,
              height: 80,
              color: Color(0xff999999),
            ),
            Padding(
              padding: const EdgeInsets.only(top: 0),
              child: Text(
                '暂无相关数据 /(ㄒoㄒ)/~~',
                style: TextStyle(
                  fontSize: 13,
                  color: Color(0xff999999),
                ),
              ),
            ),
          ],
        ),
      );
}
  • 准备一个加载器状态的Model,在Provider中,要求变化的值能处理相关变化,必须继承相关类,如:ChangeNotifier(常用)等。
import 'package:flutter/cupertino.dart';
import 'package:project_name/core/widget/loader_container.dart'
    as LoaderStater;

/// 加载器的状态Model类
class LoaderStateModel extends ChangeNotifier {
  LoaderStater.LoaderState _state = LoaderStater.LoaderState.Loading;

  /// 构造函数
  LoaderStateModel([this._state]) {
    this._state = this._state ?? LoaderStater.LoaderState.Loading;
  }

  /// 修改状态
  ///
  /// [state]  LoaderState的取值
  void changeState(LoaderStater.LoaderState state) {
    _state = state;
    notifyListeners();
  }

  /// 获取当前加载器的状态
  LoaderStater.LoaderState get state => _state;
}
  • 页面中处理加载器状态, 首页要给Page包装一个Provider,这里写在跳转这个页面的方法处。
return MultiProvider(providers: [
        ChangeNotifierProvider<LoaderStateModel>.value(
            value: LoaderStateModel(LoaderState.Succeed))
      ], child: PresaleOrderDetailInfoPage());
  • 之前包装过Provider,那么在PresaleOrderDetailInfoPage中就能接收到Provider共享的状态值了。一般情况下,我们接收值使用Provider.of<xxx>(context)来接收,如果只是接收值可以考虑使用Consumer或其相关扩展函数。
class PresaleOrderDetailInfoPage extends StatefulWidget {
 PresaleOrderDetailInfoPage({Key key}) : super(key: key);

 @override
 State<StatefulWidget> createState() => _PresaleOrderDetailInfoPageState();
}

class _PresaleOrderDetailInfoPageState
   extends BasePageState<PresaleOrderDetailInfoPage>
   with TickerProviderStateMixin {
/// 加载状体模型
 LoaderStateModel _loaderStateModel;

 /// 调用网络获取订单详情信息
 void _getOrderDetailInfo() async {
   _loaderStateModel.changeState(LoaderState.Loading);
   Future.delayed(Duration(seconds: 3), () { //模拟数据调用
     _loaderStateModel.changeState(LoaderState.Succeed);
   });
 }

@override
 void didChangeDependencies() {
   super.didChangeDependencies();

   /// 初始化加载器的状态
   final loaderStateModel =
       Provider.of<LoaderStateModel>(context);
   if (this._loaderStateModel != loaderStateModel) {
     this._loaderStateModel = loaderStateModel;
     Future.microtask(() => _getOrderDetailInfo()); //必须要这么做,不然可能会抛出异常,使用Future.microtask执行初始化任务
   }
 }
@override
 Widget build(BuildContext context) => MaterialApp(
       theme: ThemeData(primaryColor: Colors.white),
       home: Scaffold(
         appBar: _buildAppBar(),
         backgroundColor: AppConstants.backgroundColor,
         body: _buildContentView(context),
       ),
     );

 /// 构建AppBar视图
 Widget _buildAppBar() => AppBar(
     backgroundColor: Colors.white,
     title: Text('订单详情', style: TextStyle(color: Color(0xff333333))),
     centerTitle: true,
     elevation: 0.5,
     leading: FlatButton(
       child: Image.asset(_imageAssetPaths['NavigationBack'],
           fit: BoxFit.contain, color: Color(0xff333333)),
       padding: const EdgeInsets.all(20),
       shape: CircleBorder(),
       onPressed: () => finish(),
     ));

 /// 构建页面主视图
 Widget _buildContentView(BuildContext context) => Consumer<LoaderStateModel>( //使用Consumer处理共享值
       builder: (context, state, _) => LoaderContainer(
         loaderState: state.state,
         onReload: _getOrderDetailInfo,
         contentView: Column(
           crossAxisAlignment: CrossAxisAlignment.stretch,
           children: <Widget>[
             Expanded(
               child: SingleChildScrollView(
                 physics: BouncingScrollPhysics(),
                 child: Column(
                   crossAxisAlignment: CrossAxisAlignment.stretch,
                   children: <Widget>[
                     _buildOrderHeaderView(),
                     _buildRefundProcessView(),
                     _buildReceiverAddressView(),
                     _buildGoodsInfoContainerView(),
                     _buildStaticsInfoContentView(),
                   ],
                 ),
               ),
             ),
             _buildOperatorContainerView(),
           ],
         ),
       ),
     );

///...省略一些不重要的代码
}

最后,希望各位学习这个框架的朋友们加油,我也还在更加熟悉这个框架。目前来说,已经尝到这个框架所带来的甜处了。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容