Flutter&Dart技术收藏flutter

fish_redux使用详解---看完就会用!

2020-08-12  本文已影响0人  小呆呆666

说句心里话,这篇文章,来来回回修改了很多次,如果认真看完这篇文章,还不会写fish_redux,请在评论里喷我。

前言

来学学难搞的fish_redux框架吧,这个框架,官方的文档真是一言难尽,比flutter_bloc官网的文档真是逊色太多了,但是一旦知道怎么写,页面堆起来也是非常爽呀,结构分明,逻辑也会错落有致。

其实在当时搞懂这个框架的时候,就一直想写一篇文章记录下,但是因为忙(lan),导致一直没写,现在觉得还是必须把使用的过程记录下,毕竟刚上手这个框架是个蛋痛的过程,必须要把这个过程做个记录。

这不仅仅是记录的文章,文中所给出的示例,也是我重新构思去写的,过程也是力求阐述清楚且详细。

img

几个问题点

如果你在使用fish_redux的过程中遇到过上述的问题,那就来看看这篇文章吧!这里,会解答上面所有的问题点!

准备

引入

fish_redux相关地址

我用的是0.3.X的版本,算是第三版,相对于前几版,改动较大

fish_redux: ^0.3.4
#演示列表需要用到的库
dio: ^3.0.9    #网络请求框架
json_annotation: ^2.4.0 #json序列化和反序列化用的

开发插件

创建

image-20200808181242775 image-20200808181325258 image-20200808181410600 image-20200808181550186 img

开发流程

redux流程

img

fish_redux流程

范例说明

这边写几个示例,来演示fish_redux的使用

计数器

效果图

fish_redux中count

标准模式

///需要使用hide隐藏Page
import 'package:flutter/cupertino.dart'hide Page;
import 'package:flutter/material.dart' hide Page;

void main() {
  runApp(MyApp());
}

Widget createApp() {
  ///定义路由
  final AbstractRoutes routes = PageRoutes(
    pages: <String, Page<Object, dynamic>>{
      "CountPage": CountPage(),
    },
  );

  return MaterialApp(
    title: 'FishDemo',
    home: routes.buildPage("CountPage", null), //作为默认页面
    onGenerateRoute: (RouteSettings settings) {
      //ios页面切换风格
      return CupertinoPageRoute(builder: (BuildContext context) {
        return routes.buildPage(settings.name, settings.arguments);
      })
//      Material页面切换风格
//      return MaterialPageRoute<Object>(builder: (BuildContext context) {
//        return routes.buildPage(settings.name, settings.arguments);
//      });
    },
  );
}
class CountState implements Cloneable<CountState> {
  int count;

  @override
  CountState clone() {
    return CountState()..count = count;
  }
}

CountState initState(Map<String, dynamic> args) {
  return CountState()..count = 0;
}
Widget buildView(CountState state, Dispatch dispatch, ViewService viewService) {
  return _bodyWidget(state, dispatch);
}

Widget _bodyWidget(CountState state, Dispatch dispatch) {
  return Scaffold(
    appBar: AppBar(
      title: Text("FishRedux"),
    ),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('You have pushed the button this many times:'),
          ///使用state中的变量,控住数据的变换
          Text(state.count.toString()),
        ],
      ),
    ),
    floatingActionButton: FloatingActionButton(
      onPressed: () {
        ///点击事件,调用action 计数自增方法
        dispatch(CountActionCreator.countIncrease());
      },
      child: Icon(Icons.add),
    ),
  );
}
enum CountAction { increase, updateCount }

class CountActionCreator {
  ///去effect层去处理自增数据
  static Action countIncrease() {
    return Action(CountAction.increase);
  }
  ///去reducer层更新数据,传参可以放在Action类中的payload字段中,payload是dynamic类型,可传任何类型
  static Action updateCount(int count) {
    return Action(CountAction.updateCount, payload: count);
  }
}
Effect<CountState> buildEffect() {
  return combineEffects(<Object, Effect<CountState>>{
    CountAction.increase: _onIncrease,
  });
}
///自增数
void _onIncrease(Action action, Context<CountState> ctx) {
  ///处理自增数逻辑
  int count = ctx.state.count + 1;
  ctx.dispatch(CountActionCreator.updateCount(count));
}
Reducer<CountState> buildReducer() {
  return asReducer(
    <Object, Reducer<CountState>>{
      CountAction.updateCount: _updateCount,
    },
  );
}
///通知View层更新界面
CountState _updateCount(CountState state, Action action) {
  final CountState newState = state.clone();
  newState..count = action.payload;
  return newState;
}

优化

搞起来

Widget buildView(CountState state, Dispatch dispatch, ViewService viewService) {
  return _bodyWidget(state, dispatch);
}

Widget _bodyWidget(CountState state, Dispatch dispatch) {
  return Scaffold(
    appBar: AppBar(
      title: Text("FishRedux"),
    ),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('You have pushed the button this many times:'),
          Text(state.count.toString()),
        ],
      ),
    ),
    floatingActionButton: FloatingActionButton(
      onPressed: () {
        ///点击事件,调用action 计数自增方法
        dispatch(CountActionCreator.updateCount());
      },
      child: Icon(Icons.add),
    ),
  );
}
enum CountAction { updateCount }

class CountActionCreator {
  ///去reducer层更新数据,传参可以放在Action类中的payload字段中,payload是dynamic类型,可传任何类型
  static Action updateCount() {
    return Action(CountAction.updateCount);
  }
}
Reducer<CountState> buildReducer() {
  return asReducer(
    <Object, Reducer<CountState>>{
      CountAction.updateCount: _updateCount,
    },
  );
}
///通知View层更新界面
CountState _updateCount(CountState state, Action action) {
  final CountState newState = state.clone();
  newState..count = state.count + 1;
  return newState;
}

搞定

img

页面跳转

效果图

fish_redux中jump

实现

Widget createApp() {
  ///定义路由
  final AbstractRoutes routes = PageRoutes(
    pages: <String, Page<Object, dynamic>>{
      ///计数器模块演示
      "CountPage": CountPage(),
      ///页面传值跳转模块演示
      "FirstPage": FirstPage(),
      "SecondPage": SecondPage(),
    },
  );

  return MaterialApp(
    title: 'FishRedux',
    home: routes.buildPage("FirstPage", null), //作为默认页面
    onGenerateRoute: (RouteSettings settings) {
      //ios页面切换风格
      return CupertinoPageRoute(builder: (BuildContext context) {
        return routes.buildPage(settings.name, settings.arguments);
      });
    },
  );
}

FirstPage

class FirstState implements Cloneable<FirstState> {
  ///传递给下个页面的值
  static const String fixedMsg = "\n我是FirstPage页面传递过来的数据:FirstValue";
  ///展示传递过来的值
  String msg;

  @override
  FirstState clone() {
    return FirstState()..msg = msg;
  }
}

FirstState initState(Map<String, dynamic> args) {
  return FirstState()..msg = "\n暂无";
}
Widget buildView(FirstState state, Dispatch dispatch, ViewService viewService) {
  return _bodyWidget(state, dispatch);
}

Widget _bodyWidget(FirstState state, Dispatch dispatch) {
  return Scaffold(
    appBar: AppBar(
      title: Text("FirstPage"),
    ),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('下方数据是SecondPage页面传递过来的:'),
          Text(state.msg),
        ],
      ),
    ),
    floatingActionButton: FloatingActionButton(
      onPressed: () {
        ///跳转到Second页面
        dispatch(FirstActionCreator.toSecond());
      },
      child: Icon(Icons.arrow_forward),
    ),
  );
}
enum FirstAction { toSecond , updateMsg}

class FirstActionCreator {
  ///跳转到第二个页面
  static Action toSecond() {
    return const Action(FirstAction.toSecond);
  }
  ///拿到第二个页面返回的数据,执行更新数据操作
  static Action updateMsg(String msg) {
    return Action(FirstAction.updateMsg, payload: msg);
  }
}
/// 使用hide方法,隐藏系统包里面的Action类
import 'package:flutter/cupertino.dart' hide Action;

Effect<FirstState> buildEffect() {
  return combineEffects(<Object, Effect<FirstState>>{
    FirstAction.toSecond: _toSecond,
  });
}

void _toSecond(Action action, Context<FirstState> ctx) async{
  ///页面之间传值;这地方必须写个异步方法,等待上个页面回传过来的值;as关键字是类型转换
  var result = await Navigator.of(ctx.context).pushNamed("SecondPage", arguments: {"firstValue": FirstState.fixedMsg});
  ///获取到数据,更新页面上的数据
  ctx.dispatch(FirstActionCreator.updateMsg( (result as Map)["secondValue"]) );
}
Reducer<FirstState> buildReducer() {
  return asReducer(
    <Object, Reducer<FirstState>>{
      FirstAction.updateMsg: _updateMsg,
    },
  );
}

FirstState _updateMsg(FirstState state, Action action) {
  return state.clone()..msg = action.payload;
}

SecondPage

class SecondState implements Cloneable<SecondState> {
  ///传递给下个页面的值
  static const String fixedMsg = "\n我是SecondPage页面传递过来的数据:SecondValue";
  ///展示传递过来的值
  String msg;

  @override
  SecondState clone() {
    return SecondState()..msg = msg;
  }
}

SecondState initState(Map<String, dynamic> args) {
  ///获取上个页面传递过来的数据
  return SecondState()..msg = args["firstValue"];
}
Widget buildView(SecondState state, Dispatch dispatch, ViewService viewService) {
  return WillPopScope(
    child: _bodyWidget(state),
    onWillPop: () {
      dispatch(SecondActionCreator.backFirst());
      ///true:表示执行页面返回    false:表示不执行返回页面操作,这里因为要传值,所以接管返回操作
      return Future.value(false);
    },
  );
}

Widget _bodyWidget(SecondState state) {
  return Scaffold(
    appBar: AppBar(
      title: Text("SecondPage"),
    ),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('下方数据是FirstPage页面传递过来的:'),
          Text(state.msg),
        ],
      ),
    ),
  );
}
enum SecondAction { backFirst }

class SecondActionCreator {
  ///返回到第一个页面,然后从栈中移除自身,同时传回去一些数据
  static Action backFirst() {
    return Action(SecondAction.backFirst);
  }
}
///隐藏系统包中的Action类
import 'package:flutter/cupertino.dart' hide Action;

Effect<SecondState> buildEffect() {
  return combineEffects(<Object, Effect<SecondState>>{
    SecondAction.backFirst: _backFirst,
  });
}

void _backFirst(Action action, Context<SecondState> ctx) {
  ///pop当前页面,并且返回相应的数据
  Navigator.pop(ctx.context, {"secondValue": SecondState.fixedMsg});
}

搞定

img

列表文章

00685430

列表展示-网络请求

效果图

fish_redux中list

准备

image-20200810225418771
void main() {
  runApp(createApp());
}

Widget createApp() {
  ///定义路由
  final AbstractRoutes routes = PageRoutes(
    pages: <String, Page<Object, dynamic>>{
      ///导航页面
      "GuidePage": GuidePage(),
      ///计数器模块演示
      "CountPage": CountPage(),
      ///页面传值跳转模块演示
      "FirstPage": FirstPage(),
      "SecondPage": SecondPage(),
      ///列表模块演示
      "ListPage": ListPage(),
    },
  );

  return MaterialApp(
    title: 'FishRedux',
    home: routes.buildPage("GuidePage", null), //作为默认页面
    onGenerateRoute: (RouteSettings settings) {
      //ios页面切换风格
      return CupertinoPageRoute(builder: (BuildContext context) {
        return routes.buildPage(settings.name, settings.arguments);
      });
    },
  );
}

流程

初始化列表模块

image-20200812140314075

item模块

按照流程走

准备工作

文件结构

image-20200812141416796

OK,bean文件搞定了,再来看看,item文件中的文件,这里component文件不需要改动,所以这地方,我们只需要看:state.dart,view.dart

import 'package:fish_redux/fish_redux.dart';
import 'package:fish_redux_demo/list/bean/item_detail_bean.dart';

class ItemState implements Cloneable<ItemState> {
  Datas itemDetail;

  ItemState({this.itemDetail});

  @override
  ItemState clone() {
    return ItemState()
        ..itemDetail = itemDetail;
  }
}

ItemState initState(Map<String, dynamic> args) {
  return ItemState();
}
Widget buildView(ItemState state, Dispatch dispatch, ViewService viewService) {
  return _bodyWidget(state);
}

Widget _bodyWidget(ItemState state) {
  return Card(
    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
    elevation: 5,
    margin: EdgeInsets.only(left: 20, right: 20, top: 20),
    child: Row(
      children: <Widget>[
        //左边图片
        Container(
          margin: EdgeInsets.all(10),
          width: 180,
          height: 100,
          child: Image.network(
            state.itemDetail.envelopePic,
            fit: BoxFit.fill,
          ),
        ),
        //右边的纵向布局
        _rightContent(state),
      ],
    ),
  );
}

///item中右边的纵向布局,比例布局
Widget _rightContent(ItemState state) {
  return Expanded(
      child: Container(
    margin: EdgeInsets.all(10),
    height: 120,
    child: Column(
      mainAxisAlignment: MainAxisAlignment.start,
      children: <Widget>[
        //标题
        Expanded(
          flex: 2,
          child: Container(
            alignment: Alignment.centerLeft,
            child: Text(
              state.itemDetail.title,
              style: TextStyle(fontSize: 16),
              maxLines: 1,
              overflow: TextOverflow.ellipsis,
            ),
          ),
        ),
        //内容
        Expanded(
            flex: 4,
            child: Container(
              alignment: Alignment.centerLeft,
              child: Text(
                state.itemDetail.desc,
                style: TextStyle(fontSize: 12),
                maxLines: 3,
                overflow: TextOverflow.ellipsis,
              ),
            )),
        Expanded(
          flex: 3,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.end,
            children: <Widget>[
              //作者
              Row(
                children: <Widget>[
                  Text("作者:", style: TextStyle(fontSize: 12)),
                  Expanded(
                    child: Text(state.itemDetail.author,
                        style: TextStyle(color: Colors.blue, fontSize: 12),
                        overflow: TextOverflow.ellipsis),
                  )
                ],
              ),
              //时间
              Row(children: <Widget>[
                Text("时间:", style: TextStyle(fontSize: 12)),
                Expanded(
                  child: Text(state.itemDetail.niceDate,
                      style: TextStyle(color: Colors.blue, fontSize: 12),
                      overflow: TextOverflow.ellipsis),
                )
              ])
            ],
          ),
        ),
      ],
    ),
  ));
}

item模块,就这样写完了,不需要改动什么了,接下来看看List模块

列表模块逻辑完善

首先最重要的,我们需要将adapter建立起来,并和page绑定

adapter创建及其绑定

class ListItemAdapter extends SourceFlowAdapter<ListState> {
  static const String item_style = "project_tab_item";

  ListItemAdapter()
      : super(
          pool: <String, Component<Object>>{
            ///定义item的样式
            item_style: ItemComponent(),
          },
        );
}
class ListState extends MutableSource implements Cloneable<ListState> {
  ///这地方一定要注意,List里面的泛型,需要定义为ItemState
  ///怎么更新列表数据,只需要更新这个items里面的数据,列表数据就会相应更新
  ///使用多样式,请写出  List<Object> items;
  List<ItemState> items;

  @override
  ListState clone() {
    return ListState()..items = items;
  }

  ///使用上面定义的List,继承MutableSource,就把列表和item绑定起来了
  @override
  Object getItemData(int index) => items[index];

  @override
  String getItemType(int index) => ListItemAdapter.item_style;

  @override
  int get itemCount => items.length;

  @override
  void setItemData(int index, Object data) {
    items[index] = data;
  }
}

ListState initState(Map<String, dynamic> args) {
  return ListState();
}
class ListPage extends Page<ListState, Map<String, dynamic>> {
  ListPage()
      : super(
          initState: initState,
          effect: buildEffect(),
          reducer: buildReducer(),
          view: buildView,
          dependencies: Dependencies<ListState>(
              ///绑定Adapter
              adapter: NoneConn<ListState>() + ListItemAdapter(),
              slots: <String, Dependent<ListState>>{}),
          middleware: <Middleware<ListState>>[],
        );
}

正常page页面编辑

整体流程

Widget buildView(ListState state, Dispatch dispatch, ViewService viewService) {
  return Scaffold(
    appBar: AppBar(
      title: Text("ListPage"),
    ),
    body: _itemWidget(state, viewService),
  );
}

Widget _itemWidget(ListState state, ViewService viewService) {
  if (state.items != null) {
    ///使用列表
    return ListView.builder(
      itemBuilder: viewService.buildAdapter().itemBuilder,
      itemCount: viewService.buildAdapter().itemCount,
    );
  } else {
    return Center(
      child: CircularProgressIndicator(),
    );
  }
}
enum ListAction { updateItem }

class ListActionCreator {
  static Action updateItem(var list) {
    return Action(ListAction.updateItem, payload: list);
  }
}
Effect<ListState> buildEffect() {
  return combineEffects(<Object, Effect<ListState>>{
    ///进入页面就执行的初始化操作
    Lifecycle.initState: _init,
  });
}

void _init(Action action, Context<ListState> ctx) async {
  String apiUrl = "https://www.wanandroid.com/project/list/1/json";
  Response response = await Dio().get(apiUrl);
  ItemDetailBean itemDetailBean =
      ItemDetailBean.fromJson(json.decode(response.toString()));
  List<Datas> itemDetails = itemDetailBean.data.datas;
  ///构建符合要求的列表数据源
  List<ItemState> items = List.generate(itemDetails.length, (index) {
    return ItemState(itemDetail: itemDetails[index]);
  });
  ///通知更新列表数据源
  ctx.dispatch(ListActionCreator.updateItem(items));
}
Reducer<ListState> buildReducer() {
  return asReducer(
    <Object, Reducer<ListState>>{
      ListAction.updateItem: _updateItem,
    },
  );
}

ListState _updateItem(ListState state, Action action) {
  return state.clone()..items = action.payload;
}

列表修改-单item刷新

效果图

list_editjump

结构

image-20200813171905618

列表模块

class ListEditState extends MutableSource implements Cloneable<ListEditState> {
  List<ItemState> items;

  @override
  ListEditState clone() {
    return ListEditState()..items = items;
  }

  @override
  Object getItemData(int index) => items[index];

  @override
  String getItemType(int index) => ListItemAdapter.itemName;

  @override
  int get itemCount => items.length;

  @override
  void setItemData(int index, Object data) {
    items[index] = data;
  }
}

ListEditState initState(Map<String, dynamic> args) {
  return ListEditState()
    ..items = [
      ItemState(id: 1, title: "列表Item-1", itemStatus: false),
      ItemState(id: 2, title: "列表Item-2", itemStatus: false),
      ItemState(id: 3, title: "列表Item-3", itemStatus: false),
      ItemState(id: 4, title: "列表Item-4", itemStatus: false),
      ItemState(id: 5, title: "列表Item-5", itemStatus: false),
      ItemState(id: 6, title: "列表Item-6", itemStatus: false),
    ];
}
Widget buildView(ListEditState state, Dispatch dispatch, ViewService viewService) {
  return Scaffold(
    appBar: AppBar(
      title: Text("ListEditPage"),
    ),
    body: ListView.builder(
      itemBuilder: viewService.buildAdapter().itemBuilder,
      itemCount: viewService.buildAdapter().itemCount,
    ),
  );
}
class ListItemAdapter extends SourceFlowAdapter<ListEditState> {
  static const String itemName = "item";

  ListItemAdapter()
      : super(
          pool: <String, Component<Object>>{itemName: ItemComponent()},
        );
}
class ListEditPage extends Page<ListEditState, Map<String, dynamic>> {
  ListEditPage()
      : super(
    initState: initState,
    view: buildView,
    dependencies: Dependencies<ListEditState>(
        ///绑定适配器
        adapter: NoneConn<ListEditState>() + ListItemAdapter(),
        slots: <String, Dependent<ListEditState>>{}),
    middleware: <Middleware<ListEditState>>[],
  );
}

item模块

class ItemState implements Cloneable<ItemState> {
  int id;
  String title;
  bool itemStatus;


  ItemState({this.id, this.title, this.itemStatus});

  @override
  ItemState clone() {
    return ItemState()
      ..title = title
      ..itemStatus = itemStatus
      ..id = id;
  }
}

ItemState initState(Map<String, dynamic> args) {
  return ItemState();
}
Widget buildView(ItemState state, Dispatch dispatch, ViewService viewService) {
  return Container(
    child: InkWell(
      onTap: () {},
      child: ListTile(
        title: Text(state.title),
        trailing: Checkbox(
          value: state.itemStatus,
          ///Checkbox的点击操作:状态变更
          onChanged: (value) => dispatch(ItemActionCreator.onChange(state.id)),
        ),
      ),
    ),
  );
}
enum ItemAction { onChange }

class ItemActionCreator {
  //状态改变
  static Action onChange(int id) {
    return Action(ItemAction.onChange, payload: id);
  }
}
Reducer<ItemState> buildReducer() {
  return asReducer(
    <Object, Reducer<ItemState>>{
      ItemAction.onChange: _onChange,
    },
  );
}

ItemState _onChange(ItemState state, Action action) {
  if (state.id == action.payload) {
    return state.clone()..itemStatus = !state.itemStatus;
  }
  ///这地方一定要注意,要返回:state;不能返回:state.clone(),否则会造成后续更新失灵
  return state;
}

多样式列表

注意:如果使用多样式,items的列表泛型不要写成ItemState,写成Object就行了;在下面代码,我们可以看到,实现的getItemData()方法返回的类型是Object,所以Items的列表泛型写成Object,是完全可以的。

下述代码可做思路参考

class ListState extends MutableSource implements Cloneable<PackageCardState> {
    List<Object> items;

    @override
    ListState clone() {
        return PackageCardState()..items = items;
    }

    @override
    Object getItemData(int index) => items[index];

    @override
    String getItemType(int index) {
        if(items[index] is OneState) {
            return PackageCardAdapter.itemStyleOne;
        }else{
            return PackageCardAdapter.itemStyleTwo;
        }
    }

    @override
    int get itemCount => items.length;

    @override
    void setItemData(int index, Object data) => items[index] = data;
}

列表存在的问题+解决方案

列表多item刷新问题

这里搞定了单item刷新场景,还存在一种多item刷新的场景

举例:假设一种场景,对于上面的item只能单选,一个item项被选中,其它item状态被重置到未选状态,具体效果看下方效果图

单选模式

下述代码为整体流程

Widget buildView(ItemState state, Dispatch dispatch, ViewService viewService) {
  return InkWell(
    onTap: () {},
    child: ListTile(
      title: Text(state.title),
      trailing: Checkbox(
        value: state.itemStatus,
        ///CheckBox的点击操作:状态变更
        onChanged: (value) {
          //单选模式,清除选中的item,以便做单选
          dispatch(ItemActionCreator.clear());

          //刷新选中item
          dispatch(ItemActionCreator.onChange(state.id));
        }
      ),
    ),
  );
}
enum ItemAction {
  onChange,
  clear,
}

class ItemActionCreator {
  //状态改变
  static Action onChange(int id) {
    return Action(ItemAction.onChange, payload: id);
  }

  //清除改变的状态
  static Action clear() {
    return Action(ItemAction.clear);
  }
}
Reducer<ItemState> buildReducer() {
  return asReducer(
    <Object, Reducer<ItemState>>{
      ItemAction.onChange: _onChange,
      ItemAction.clear: _clear,
    },
  );
}

ItemState _onChange(ItemState state, Action action) {
  if (state.id == action.payload) {
    return state.clone()..itemStatus = !state.itemStatus;
  }

  ///这地方一定要注意,要返回:state;不能返回:state.clone(),否则会造成后续更新失灵
  return state;
}

///单选模式
ItemState _clear(ItemState state, Action action) {
  if (state.itemStatus) {
    return state.clone()..itemStatus = false;
  }

  ///这地方一定要注意,要返回:state;不能返回:state.clone(),否则会造成后续更新失灵
  return state;
}

这个问题实际上解决起来很简单,但是如果一直在 _onChange 方法重置状态,你会发现和你预期的结果一直对不上;完整且详细的效果,可以去看demo里面代码

搞定

img

全局模式

效果图

fish_redux_switch

开搞

store模块

image-20200812162317741
abstract class GlobalBaseState{
  Color themeColor;
}

class GlobalState implements GlobalBaseState, Cloneable<GlobalState>{
  @override
  Color themeColor;

  @override
  GlobalState clone() {
    return GlobalState();
  }
}
enum GlobalAction { changeThemeColor }

class GlobalActionCreator{
  static Action onChangeThemeColor(){
    return const Action(GlobalAction.changeThemeColor);
  }
}
import 'package:flutter/material.dart' hide Action;

Reducer<GlobalState> buildReducer(){
  return asReducer(
    <Object, Reducer<GlobalState>>{
      GlobalAction.changeThemeColor: _onChangeThemeColor,
    },
  );
}

List<Color> _colors = <Color>[
  Colors.green,
  Colors.red,
  Colors.black,
  Colors.blue
];

GlobalState _onChangeThemeColor(GlobalState state, Action action) {
  final Color next =
  _colors[((_colors.indexOf(state.themeColor) + 1) % _colors.length)];
  return state.clone()..themeColor = next;
}
/// 建立一个AppStore
/// 目前它的功能只有切换主题
class GlobalStore{
  static Store<GlobalState> _globalStore;
  static Store<GlobalState> get store => _globalStore ??= createStore<GlobalState>(GlobalState(), buildReducer());
}

main改动

void main() {
  runApp(createApp());
}

Widget createApp() {
  ///全局状态更新
  _updateState() {
    return (Object pageState, GlobalState appState) {
      final GlobalBaseState p = pageState;

      if (pageState is Cloneable) {
        final Object copy = pageState.clone();
        final GlobalBaseState newState = copy;
        if (p.themeColor != appState.themeColor) {
          newState.themeColor = appState.themeColor;
        }
        /// 返回新的 state 并将数据设置到 ui
        return newState;
      }
      return pageState;
    };
  }
  
  final AbstractRoutes routes = PageRoutes(
    ///全局状态管理:只有特定的范围的Page(State继承了全局状态),才需要建立和 AppStore 的连接关系
    visitor: (String path, Page<Object, dynamic> page) {
      if (page.isTypeof<GlobalBaseState>()) {
        ///建立AppStore驱动PageStore的单向数据连接: 参数1 AppStore  参数2 当AppStore.state变化时,PageStore.state该如何变化
        page.connectExtraStore<GlobalState>(GlobalStore.store, _updateState());
      }
    },

    ///定义路由
    pages: <String, Page<Object, dynamic>>{
      ///导航页面
      "GuidePage": GuidePage(),
      ///计数器模块演示
      "CountPage": CountPage(),
      ///页面传值跳转模块演示
      "FirstPage": FirstPage(),
      "SecondPage": SecondPage(),
      ///列表模块演示
      "ListPage": ListPage(),
    },
  );

  return MaterialApp(
    title: 'FishRedux',
    home: routes.buildPage("GuidePage", null), //作为默认页面
    onGenerateRoute: (RouteSettings settings) {
      //ios页面切换风格
      return CupertinoPageRoute(builder: (BuildContext context) {
        return routes.buildPage(settings.name, settings.arguments);
      });
    },
  );
}

子模块使用

class CountState implements Cloneable<CountState>,GlobalBaseState {
  int count;

  @override
  CountState clone() {
    return CountState()..count = count;
  }

  @override
  Color themeColor;
}

CountState initState(Map<String, dynamic> args) {
  return CountState()..count = 0;
}
Widget buildView(CountState state, Dispatch dispatch, ViewService viewService) {
  return _bodyWidget(state, dispatch);
}

Widget _bodyWidget(CountState state, Dispatch dispatch) {
  return Scaffold(
    appBar: AppBar(
      title: Text("FishRedux"),
      ///全局主题,仅仅在此处改动了一行
      backgroundColor: state.themeColor,
    ),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('You have pushed the button this many times:'),
          Text(state.count.toString()),
        ],
      ),
    ),
    floatingActionButton: FloatingActionButton(
      onPressed: () {
        ///点击事件,调用action 计数自增方法
        dispatch(CountActionCreator.updateCount());
      },
      child: Icon(Icons.add),
    ),
  );
}

调用

GlobalStore.store.dispatch(GlobalActionCreator.onChangeThemeColor());

搞定

全局模块优化

反思

在上面的全局模式里说了,使用全局模块,前期需要规划好字段,不然项目进行到中期的时候,想添加字段,多个模块的State会出现大范围爆红,提示去实现你添加的字段;项目开始规划好所有的字段,显然这需要全面的考虑好大部分场景,但是人的灵感总是无限的,不改代码是不可能,这辈子都不可能。只能想办法看能不能添加一次字段后,后期添加字段,并不会引起其他模块爆红,试了多次,成功的使用中间实体,来解决该问题

这里优化俩个方面

因为使用中间实体,有一些地方会出现空指针问题,我都在流程里面写清楚了,大家可以把优化流程完整看一遍哈,都配置好,后面拓展使用就不会报空指针了

优化

入口模块

void main() {
  runApp(createApp());
}

Widget createApp() {
  return MaterialApp(
    title: 'FishRedux',
    home: RouteConfig.routes.buildPage(RouteConfig.guidePage, null), //作为默认页面
    onGenerateRoute: (RouteSettings settings) {
      //ios页面切换风格
      return CupertinoPageRoute(builder: (BuildContext context) {
        return RouteConfig.routes.buildPage(settings.name, settings.arguments);
      });
    },
  );
}

///路由管理
class RouteConfig {
  ///定义你的路由名称比如   static final String routeHome = 'page/home';
  ///导航页面
  static const String guidePage = 'page/guide';

  ///计数器页面
  static const String countPage = 'page/count';

  ///页面传值跳转模块演示
  static const String firstPage = 'page/first';
  static const String secondPage = 'page/second';

  ///列表模块演示
  static const String listPage = 'page/list';
  static const String listEditPage = 'page/listEdit';

  static final AbstractRoutes routes = PageRoutes(
    pages: <String, Page<Object, dynamic>>{
      ///将你的路由名称和页面映射在一起,比如:RouteConfig.homePage : HomePage(),
      RouteConfig.guidePage: GuidePage(),
      RouteConfig.countPage: CountPage(),
      RouteConfig.firstPage: FirstPage(),
      RouteConfig.secondPage: SecondPage(),
      RouteConfig.listPage: ListPage(),
      RouteConfig.listEditPage: ListEditPage(),
    },
    visitor: StoreConfig.visitor,
  );
}

///全局模式
class StoreConfig {
  ///全局状态管理
  static _updateState() {
    return (Object pageState, GlobalState appState) {
      final GlobalBaseState p = pageState;

      if (pageState is Cloneable) {
        final Object copy = pageState.clone();
        final GlobalBaseState newState = copy;

        if (p.store == null) {
          ///这地方的判断是必须的,判断第一次store对象是否为空
          newState.store = appState.store;
        } else {
          /// 这地方增加字段判断,是否需要更新
          if ((p.store.themeColor != appState.store.themeColor)) {
            newState.store.themeColor = appState.store.themeColor;
          }

          /// 如果增加字段,同理上面的判断然后赋值...

        }

        /// 返回新的 state 并将数据设置到 ui
        return newState;
      }
      return pageState;
    };
  }

  static visitor(String path, Page<Object, dynamic> page) {
    if (page.isTypeof<GlobalBaseState>()) {
      ///建立AppStore驱动PageStore的单向数据连接
      ///参数1 AppStore  参数2 当AppStore.state变化时,PageStore.state该如何变化
      page.connectExtraStore<GlobalState>(GlobalStore.store, _updateState());
    }
  }
}

Store模块

下面俩个模块是需要改动代码的模块

abstract class GlobalBaseState{
  StoreModel store;
}

class GlobalState implements GlobalBaseState, Cloneable<GlobalState>{

  @override
  GlobalState clone() {
    return GlobalState();
  }

  @override
  StoreModel store = StoreModel(
    /// store这个变量,在这必须示例化,不然引用该变量中的字段,会报空指针
    /// 下面的字段,赋初值,就是初始时展示的全局状态
    /// 这地方初值,理应从缓存或数据库中取,表明用户选择的全局状态
    themeColor: Colors.lightBlue
  );
}

///中间全局实体
///需要增加字段就在这个实体里面添加就行了
class StoreModel {
  Color themeColor;

  StoreModel({this.themeColor});
}
Reducer<GlobalState> buildReducer(){
  return asReducer(
    <Object, Reducer<GlobalState>>{
      GlobalAction.changeThemeColor: _onChangeThemeColor,
    },
  );
}

List<Color> _colors = <Color>[
  Colors.green,
  Colors.red,
  Colors.black,
  Colors.blue
];

GlobalState _onChangeThemeColor(GlobalState state, Action action) {
  final Color next =
  _colors[((_colors.indexOf(state.store.themeColor) + 1) % _colors.length)];
  return state.clone()..store.themeColor = next;
}

下面俩个模块代码没有改动,但是为了思路完整,同样贴出来

enum GlobalAction { changeThemeColor }

class GlobalActionCreator{
  static Action onChangeThemeColor(){
    return const Action(GlobalAction.changeThemeColor);
  }
}
class GlobalStore{
  static Store<GlobalState> _globalStore;
  static Store<GlobalState> get store => _globalStore ??= createStore<GlobalState>(GlobalState(), buildReducer());
}

子模块使用

class CountState implements Cloneable<CountState>, GlobalBaseState {
  int count;

  @override
  CountState clone() {
    return CountState()
      ..count = count
      ..store = store;
  }

  @override
  StoreModel store;
}

CountState initState(Map<String, dynamic> args) {
  return CountState()..count = 0;
}
Widget buildView(CountState state, Dispatch dispatch, ViewService viewService) {
  return _bodyWidget(state, dispatch);
}

Widget _bodyWidget(CountState state, Dispatch dispatch) {
  return Scaffold(
    appBar: AppBar(
      title: Text("FishRedux"),
      ///全局主题,仅仅在此处改动了一行
      backgroundColor: state.store.themeColor,
    ),
    ///下面其余代码省略....
}

调用

GlobalStore.store.dispatch(GlobalActionCreator.onChangeThemeColor());

体验

通过上面的优化,使用体验提升不是一个级别,大大提升的全局模式的扩展性,我们就算后期增加了大量的全局字段,也可以一个个模块慢慢改,不用一次爆肝全改完,猝死的概率又大大减少了!

img

Component使用

Component是个比较常用的模块,上面使用列表的时候,就使用到了Component,这次我们来看看,在页面中直接使用Component,可插拔式使用!Component的使用总的来说是比较简单了,比较关键的是在State中建立起连接。

效果图

fish_redux中component image-20200905183821129

Component

这地方写了一个Component,代码很简单,来看看吧

这地方代码是自动生成了,没有任何改动,就不贴了

class AreaState implements Cloneable<AreaState> {
  String title;
  String text;
  Color color;

  AreaState({
    this.title = "",
    this.color = Colors.blue,
    this.text = "",
  });

  @override
  AreaState clone() {
    return AreaState()
      ..color = color
      ..text = text
      ..title = title;
  }
}
Widget buildView(
    AreaState state, Dispatch dispatch, ViewService viewService) {
  return Scaffold(
    appBar: AppBar(
      title: Text(state.title),
      automaticallyImplyLeading: false,
    ),
    body: Container(
      height: double.infinity,
      width: double.infinity,
      alignment: Alignment.center,
      color: state.color,
      child: Text(state.text),
    ),
  );
}

Page

CompPage中,没用到effete这层,就没创建该文件,老规矩,先看看state

class CompState implements Cloneable<CompState> {
  AreaState leftAreaState;
  AreaState rightAreaState;

  @override
  CompState clone() {
    return CompState()
      ..rightAreaState = rightAreaState
      ..leftAreaState = leftAreaState;
  }
}

CompState initState(Map<String, dynamic> args) {
  ///初始化数据
  return CompState()
    ..rightAreaState = AreaState(
      title: "LeftAreaComponent",
      text: "LeftAreaComponent",
      color: Colors.indigoAccent,
    )
    ..leftAreaState = AreaState(
      title: "RightAreaComponent",
      text: "RightAreaComponent",
      color: Colors.blue,
    );
}

///左边Component连接器
class LeftAreaConnector extends ConnOp<CompState, AreaState>
    with ReselectMixin<CompState, AreaState> {
  @override
  AreaState computed(CompState state) {
    return state.leftAreaState.clone();
  }

  @override
  void set(CompState state, AreaState subState) {
    state.leftAreaState = subState;
  }
}

///右边Component连接器
class RightAreaConnector extends ConnOp<CompState, AreaState>
    with ReselectMixin<CompState, AreaState> {
  @override
  AreaState computed(CompState state) {
    return state.rightAreaState.clone();
  }

  @override
  void set(CompState state, AreaState subState) {
    state.rightAreaState = subState;
  }
}
class CompPage extends Page<CompState, Map<String, dynamic>> {
  CompPage()
      : super(
          initState: initState,
          reducer: buildReducer(),
          view: buildView,
          dependencies: Dependencies<CompState>(
              adapter: null,
              slots: <String, Dependent<CompState>>{
                //绑定Component
                "leftArea": LeftAreaConnector() + AreaComponent(),
                "rightArea": RightAreaConnector() + AreaComponent(),
              }),
          middleware: <Middleware<CompState>>[],
        );
}
Widget buildView(CompState state, Dispatch dispatch, ViewService viewService) {
  return Container(
    color: Colors.white,
    child: Column(
      children: [
        ///Component组件部分
        Expanded(
          flex: 3,
          child: Row(
            children: [
              Expanded(child: viewService.buildComponent("leftArea")),
              Expanded(child: viewService.buildComponent("rightArea")),
            ],
          ),
        ),

        ///按钮
        Expanded(
            flex: 1,
            child: Center(
              child: RawMaterialButton(
                fillColor: Colors.blue,
                shape: StadiumBorder(),
                onPressed: () => dispatch(CompActionCreator.change()),
                child: Text("改变"),
              ),
            ))
      ],
    ),
  );
}
enum CompAction { change }

class CompActionCreator {
  static Action change() {
    return const Action(CompAction.change);
  }
}
Reducer<CompState> buildReducer() {
  return asReducer(
    <Object, Reducer<CompState>>{
      CompAction.change: _change,
    },
  );
}

CompState _change(CompState state, Action action) {
  final CompState newState = state.clone();
  //改变leftAreaComponent中state
  newState.leftAreaState.text = "LeftAreaState:${Random().nextInt(1000)}";
  newState.leftAreaState.color =
      Color.fromRGBO(randomColor(), randomColor(), randomColor(), 1);

  //改变rightAreaComponent中state
  newState.rightAreaState.text = "RightAreaState:${Random().nextInt(1000)}";
  newState.rightAreaState.color =
      Color.fromRGBO(randomColor(), randomColor(), randomColor(), 1);

  return newState;
}

int randomColor() {
  return Random().nextInt(255);
}

总结下

总的来说,Component的使用还是比较简单的;如果我们把某个复杂的列表提炼出一个Component的,很明显有个初始化的过程,这里我们需要将:请求参数调体或列表详情操作,在page页面处理好,然后再更新给我们绑定的子Component的State,这样就能起到初始化某个模块的作用;至于刷新,下拉等后续操作,就让Component内部自己去处理了

广播

fish_redux中是带有广播的通信方式,使用的方式很简单,这本是effect层,ctx参数自带的一个api,这里简单介绍一下

使用

enum BroadcastAction { toNotify }

class BroadcastActionCreator {
  ///广播通知
  static Action toNotify(String msg) {
    return Action(BroadcastAction.toNotify, payload: msg);
  }
}
void _backFirst(Action action, Context<SecondState> ctx) {
  //广播通信
  ctx.broadcast(BroadcastActionCreator.toNotify("页面二发送广播通知"));
}
Effect<FirstState> buildEffect() {
  return combineEffects(<Object, Effect<FirstState>>{
    //接受发送的广播消息
    BroadcastAction.toNotify: _receiveNotify,
  });
}
void _receiveNotify(Action action, Context<FirstState> ctx) async {
  ///接受广播
  print("跳转一页面:${action.payload}");
}

说明

广播的使用还是挺简单的,基本和dispatch的使用是一致的,dispatch是模块的,而broadcast是有页面栈,就能通知其他页面,很多情况下,我们在一个页面进行了操作,其他页面也需要同步做一些处理,使用广播就很简单了

注意: 广播发送和接受是一对多的关系,一处发送,可以在多处接受;和dispatch发送事件,如果在effect里面接受,在reducer就无法接受的情况是不一样的(被拦截了)

开发小技巧

弱化reducer

无限弱化了reducer层作用

Reducer<TestState> buildReducer() {
  return asReducer(
    <Object, Reducer<TestState>>{
      TestAction.onRefresh: _onRefresh,
    },
  );
}

TestState _onRefresh(TreeState state, Action action) {
  return state.clone();
}

widget组合式开发

说明

这种开发形式,可以说是个惯例,在android里面是封装一个个View,View里有对应的一套,逻辑自洽的功能,然后在主xm里面组合这些View;这种思想完全可以引申到Flutter里,而且,开发体验更上几百层楼,让你的widget组合可以更加灵活百变,百变星君

最后

Demo地址

img

Flutter状态管理和Dialog问题

flutter_bloc使用解析---骚年,你还在手搭bloc吗!

一种更优雅的Flutter Dialog解决方案

上一篇下一篇

猜你喜欢

热点阅读