Flutter用fish_redux踩坑和实战

Fish_redux开发flutter的入门教学

1.Fish_redux的初相识

Fish Redux 是一个基于 Redux 数据管理的组装式 flutter 应用框架, 它特别适用于构建中大型的复杂应用。

它的特点是配置式组装。 一方面我们将一个大的页面,对视图和数据层层拆解为互相独立的 Component|Adapter,上层负责组装,下层负责实现; 另一方面将 Component|Adapter 拆分为 View,Reducer,Effect 等相互独立的上下文无关函数。

所以它会非常干净,易维护,易协作!
我们先看看fish_redux的官方GitHub给出的架构图!


fish_redux架构图.png,来自fish_redux团队的GitHub

1.1 Fish_redux的组成

  • Page

page代表一个完整的页面,也就是我们理解的activity或者UIViewController。它由redux里面分层的viewstatereducereffectaction组成。这就是所谓的组装拔插式...你懂的(#^.^#)

class HomePage extends Page<HomeState, Map<String, dynamic>> {
 HomePage()
     : super(
           initState: initState,//生命周期
           effect: buildEffect(),//处理副作用的action
           view: buildView,//界面view
           dependencies: Dependencies<HomeState>(
               adapter: NoneConn<HomeState>() + HomeAdapter(),//高性能的listview适配器
               slots: <String, Dependent<HomeState>>{
               }),//用到的挂件,也就是组件components
           middleware: <Middleware<HomeState>>[
           ],);//oop切片,可以监听某些方法和生命周期的执行

}
  • State

state主要用来保存page或者component(页面/组件)的状态或者说属性,用来存放数据。

class HomeState implements Cloneable<HomeState> {
 EasyRefreshController refreshController;
 List<HandoverDataModel> list;
 int page;
 @override//必须复写的方法
 HomeState clone() {
   return HomeState()
   ..list = list
   ..refreshController = refreshController
   ..page = page;
 }
}
//初始化状态,也就是给页面的数据初始化等
HomeState initState(Map<String, dynamic> args) {
 HomeState homeState = HomeState();
 homeState.refreshController = new EasyRefreshController();
 homeState.list = new List();
 homeState.page = 1;
 return homeState;
}
  • Action
    action就是我们定义的意图,我们把事件或者某些操作定义成意图,通过发送dispatch特定的action,让effect或者reducer里对应的接收者进行处理!
//定义我们的意图,一般为枚举,命名最好是词达意
//Effect 接受处理的 Action,以 on{Verb} 命名
//Reducer 接受处理的 Action,以{verb} 命名
//playload用来约束参数类型
enum HomeAction { onLoad, onRefresh }

//effect的action
class HomeActionCreator {
 static Action onLoad(int page) {
   return  Action(HomeAction.onLoad,payload: page);
 }

 static Action onRefresh(){
   return  Action(HomeAction.onRefresh);
 }
}
enum HandOverAction { reloadData, loadMoreData }
// reducer的action
class HandOverActionCreator {
  static Action reloadData(List<HandoverDataModel> list) {
    return Action(HandOverAction.reloadData,payload: list);
  }

  static Action loadMoreData(List<HandoverDataModel> list) {
    return Action(HandOverAction.loadMoreData,payload: list);
  }

}
  • Reducer
    reducer主要用来接收某个意图action,主要用来处理数据方面变化,修改等操作,即更新状态stateReducer 是一个上下文无关的 pure function。
    它接收下面的参数

    • T state
    • Action action

    它主要包含三方面的信心

    • 接收一个意图action,做出数据修改。
    • 如果要修改数据,需要创建一份新的拷贝,修改在拷贝上。
    • 如果数据修改了,它会自动触发State的层层数据的拷贝,在以扁平化方式通知组件刷新。
Reducer<HomeState> buildReducer() {
  return asReducer(
    <Object, Reducer<HomeState>>{
      HandOverAction.reloadData: _reloadData,
      HandOverAction.loadMoreData:_loadMoreData,
    },
  );
}

HomeState _reloadData(HomeState state, Action action) {
  final HomeState newState = state.clone();
//  newState.list.clear();
  newState.list = action.payload;
  return newState;
}

HomeState _loadMoreData(HomeState state, Action action){
  final HomeState newState = state.clone();
  newState.list.addAll(action.payload);
  return newState;
}
  • Effect
    effect的用法跟reducer类似,也是用来接收某个意图action,但是它主要用来处理所谓的函数副作用,简单理解为数据和状态state更新变化外为的其它操作和事务.默认情况下,effect会在reducer之前被执行。
    它接收下面的参数
    • Action action
    • Context context
      • BuildContext context
      • T state
      • dispatch
      • isDisposed

Effect会接收来自View的"意图",包括对应的生命周期的回调,然后做出具体的执行,例如页面跳转,dialog弹出等!它的处理可能是一个异步函数,数据可能在过程中被修改,所以我们应该通过 context.state 获取最新数据。 如果它要修改数据,应该发一个 ActionReducer 里去处理。它对数据是只读的,不能直接去修改数据

Effect<HomeState> buildEffect() {
  return combineEffects(<Object, Effect<HomeState>>{
    Lifecycle.initState:_init,
    HomeAction.onLoad: _onLoad,
    HomeAction.onRefresh: _onRefresh,
  });
}

void _init(Action action, Context<HomeState> ctx){
  ctx.dispatch(HomeActionCreator.onRefresh());
}

void _onLoad(Action action, Context<HomeState> ctx) {
  int page = action.payload;
  println("onload$page");
  NetUtlis.get("/utdcjjb/fdjiaojiebanlog/list.do?filter={'gangweiCode_\$equal':'5'}&page=$page&rows=10&sidx=lrtime&sord=desc",success: (value){
    ctx.state.page += 1;
    HandoverEntity entity = HandoverEntity.fromJson(value);
    ctx.state.refreshController.finishLoad(noMore: entity.data.xList.length <10);
    ctx.dispatch(HandOverActionCreator.onLoadMoreData(entity.data.xList));//转发到reducer去处理数据变化
  },failure: (error){
    print(error);
  });
}

void _onRefresh(Action action,Context<HomeState> ctx){
  int page = 1;
  ctx.state.page = 1;
  NetUtlis.get("/utdcjjb/fdjiaojiebanlog/list.do?filter={'gangweiCode_\$equal':'5'}&page=$page&rows=10&sidx=lrtime&sord=desc",success: (value){
    ctx.state.page += 1;
    HandoverEntity entity = HandoverEntity.fromJson(value);
    ctx.state.refreshController.resetLoadState();
    ctx.state.refreshController.finishRefresh();
    ctx.dispatch(HandOverActionCreator.onReloadData(entity.data.xList));//转发到reducer去处理数据变化
  },failure: (error){
    print(error);
  });
}
  • View
    是一个输出Widget的上下文无关的函数。它接收下面的参数

    • T state
    • Dispatch
    • ViewService

    它主要包含三方面的信息

    • 视图完全由数据驱动
    • 视图产生的事件/回调,通过Dispatch发出"意图"(action),但绝不做出具体实现。
    • 使用依赖的组件/适配器,通过在组件上显示配置(component),再通过ViewService标准化调用。其中ViewService提供了三个能力
      • BuildContext context,获取flutter Build-Context的能力
      • Widget buildView(String name),直接创建子组件的能力
        • 这里传入的name即在Dependencies上配置的名称。
        • 创建的子组件不需要传入任何其他的参数,因为子组件需要的参数,已经通过Dependencies配置中将它们的数据关系,通过connector确立。
      • ListAdapter buildAdapter(),直接创建适配器的能力

Widget buildView(HomeState state, Dispatch dispatch, ViewService viewService) {
  final ListAdapter adapter = viewService.buildAdapter();//适配器
  return Scaffold(
    appBar: PreferredSize(
      preferredSize: Size.fromHeight(50.0),
      child: AppBar(
        automaticallyImplyLeading: false,
        centerTitle: true,
        elevation: 0,
        title: Text('值长日志'),
      ),
    ),
    body: SafeArea(
      child: EasyRefresh(
//        bottomBouncing: false,
//        topBouncing: false,
        header: ClassicalHeader(),
        footer: ClassicalFooter(
          enableInfiniteLoad: false,
          enableHapticFeedback: false,
        ),
        controller: state.refreshController,
        onRefresh: () async{
          dispatch(HomeActionCreator.onRefresh());//意图回调
        },
        onLoad: () async{
          dispatch(HomeActionCreator.onLoad(state.page));
        },
        child: ListView.builder(
          itemBuilder: adapter.itemBuilder,
          itemCount: adapter.itemCount,
        ),
      ),
    ),
  );

}
  • Connector<T,P>
    • 它表达了如何从一个大数据中读取小数据,同时对小数据的修改如何同步给大数据,这样的数据连接关系
    • 它是将一个集中式的Reducer,可以由多层次多模块的小Reducer自动拼接的关键。
      • 它大大的降低了我们使用的Redux的复杂度。我们不再关心组装过程,我们关心的核心是什么动做促使数据怎么变化。
    • 它使用在配置Dependencies中,在配置中我们就固化了大组件和小组件之间的连接关系(数据管道),所以在我们使用小组件的时候是不需要传入任何动态参数的。
class _HomeConnector extends ConnOp<HomeState, List<ItemBean>> {
//HomeState是大page的数据,handover是列表item的数据,通过大数据给小数据赋值
  @override 
  List<ItemBean> get(HomeState state) {
    if(state.list?.isNotEmpty == true){
      return state.list.map<ItemBean>((m){
        HandOverState handOverState = new HandOverState();
        handOverState.handoverDataModel = m;
            return ItemBean("handover", handOverState);
      }).toList();
    }else{
      return<ItemBean>[];
    }
  }
//小数据发生改变,回传给大数据
  @override
  void set(HomeState state, List<ItemBean> items) {
    if(items.isNotEmpty == true){
      state.list = List<HandoverDataModel>.from(items.map((m){
        return m;
    }).toList());
  }else{
      state.list = new List();
    }
  }

  @override
  subReducer(reducer) {
    // TODO: implement subReducer
    return super.subReducer(reducer);
  }
}

  • Dependencies
    是一个表达组件之间依赖关系的结构。它接收两个字段

    • slots
      • <String,Dependent>{}
    • adapter

    它主要包含三方面的信息

    • slots, 组件依赖的插槽
    • adapter, 组件依赖的具体适配器(用来构建高性能的ListView)。
    • Dependent 是subComponent|subAdapter + connector的组合。
    • 一个组件的Reducer由Component自身配置的Reducer和它的Dependencies下的所有子Reducers自动复合而成。
///register in component,真实数据没接入component,所以拷贝的官方数据
class ItemComponent extends ItemComponent<ItemState> {
  ItemComponent()
      : super(
          view: buildItemView,
          reducer: buildItemReducer(),
          dependencies: Dependencies<ItemState>(
            slots: <String, Dependent<ItemState>>{
              'appBar': AppBarComponent().asDependent(AppBarConnector()),
              'body': ItemBodyComponent().asDependent(ItemBodyConnector()),
              'ad_ball': ADBallComponent().asDependent(ADBallConnector()),
              'bottomBar': BottomBarComponent().asDependent(BottomBarConnector()),
            },
          ),
        );
}

///call in view
Widget buildItemView(ItemState state, Dispatch dispatch, ViewService service) {
  return Scaffold(
      body: Stack(
        children: <Widget>[
          service.buildComponent('body'),
          service.buildComponent('ad_ball'),
          Positioned(
            child: service.buildComponent('bottomBar'),
            left: 0.0,
            bottom: 0.0,
            right: 0.0,
            height: 100.0,
          ),
        ],
      ),
      appBar: AppbarPreferSize(child: service.buildComponent('appBar')));
}
  • Adapter
    首先要明白Adapter是一种组件化的抽象,主要是运用在ListView的场景,提高性能和流畅度。一个AdapterComponent几乎是一致的,除了以下几点
    • Component生成一个Widget,Adapter生成一个ListAdapter,ListAdapter有能力生成一组Widget

      • 不具体生成Widget,而是一个ListAdapter,能非常大的提升页面帧率和流畅度。
    • Effect-Lifecycle-Promote

      • ComponentEffect 是跟着 Widget 的生命周期走的,AdapterEffect 是跟着上一级的 Widget 的生命周期走。
      • Effect 提升,极大的解除了业务逻辑和视图生命的耦合,即使它的展示还未出现,的其他模块依然能通过 dispatch-api,调用它的能力。
  • appear|disappear 的通知
    • 由于 Effect 生命周期的提升,我们就能更加精细的区分 init|disposeappear|disappear。而这在 Component 的模型中是无法区分的
  • Reducer is long-lived, Effect is medium-lived, View is short-lived.

其中Adapter有三种实现,我们需要继承其中一种

  • DynamicFlowAdapter(模版是一个 Map,接受一个数组类型的数据驱动)
  • StaticFlowAdapter(模版是一个 Array,接受 Object|Map 的数据驱动,模版接收一个 Dependent 的数组,每一个 Dependent 可以是 Component 或者 Adapter + Connector<T,P> 的组合)
  • CustomAdapter(对大 Cell 的自定义实现
    要素和 Component 类似,不一样的地方是 Adapter 的视图部分返回的是一个 ListAdapter)
  • Lifecycle
    默认的所有生命周期,本质上都来自于 flutter State 中的生命周期
    • initState
    • didChangeDependencies
    • build
    • didUpdateWidget
    • deactivate
    • dispose
  • 在组件内,Reducer 的生命周期是和页面一致的,Effect 和 View 的生命周期是和组件的 Widget 一致的。
  • 在适配器中,Reducer 的生命周期是和页面一致的,Effect 的生命周期是和 ListView 的生命周期一致,View 的生命周期是短暂的(划入不可见区域即销毁)。同时增加了 appear 和 disappear 的生命周期, 代表这个 adapter 管理的视图数组,刚进入显示区和完全离开显示区的回调。

我们了解完基础的fish_redux库的一些基本组成了,在这个基础上,我们来看一下用fish_redux编写flutter的目录结构,主要借助于闲鱼团队提供的插件FishReduxTemplate我们省了很多功夫(剩下的就是命名和自己组合的能力了)

lib//主要编写代码目录
├── app.dart//主入口
├── entity_factory.dart//模型工厂辅助类
├── home//主页面
│   ├── action.dart
│   ├── effect.dart
│   ├── home_adapter//适配器
│   │   ├── action.dart
│   │   ├── adapter.dart
│   │   └── reducer.dart
│   ├── home_component//组件
│   │   ├── action.dart
│   │   ├── component.dart
│   │   ├── effect.dart
│   │   ├── reducer.dart
│   │   ├── state.dart
│   │   └── view.dart
│   ├── page.dart
│   ├── state.dart
│   └── view.dart
├── login//login页面
│   ├── action.dart
│   ├── effect.dart
│   ├── page.dart
│   ├── state.dart
│   └── view.dart
├── main.dart
└── network//网络请求和模型实体类
    ├── handover_entity.dart
    ├── netuntil.dart
    └── user_info_entity.dart

看完以上我们再来了解一下fish_redux的的工作流程:


flutter流程.png--网上来源

整体流程如下:
用户进行某个操作----->然后调用context.dispatch方法发送一个由ActionCreator创建的Action----->effect接收并处理,然后dispatch给reducer----->reducer接收并产生新的state----->state更新导致界面显示的刷新

2.fish_redux实战,接入网络,刷新等等实际应用场景

2.1 创建应用的根Widget

新建一个app.dart用来创建应用的根Widget,app.dart如下:

import 'package:fish_redux/fish_redux.dart';
import 'package:flutter_redux_demo/home/page.dart';
import 'package:flutter_redux_demo/login/page.dart';

Widget createApp() {
  final AbstractRoutes routes = PageRoutes(
    pages: <String, Page<Object, dynamic>>{
      /// 注册登陆主页面
      "login": LoginPage(),
      "home": HomePage(),
    },
    visitor: (String path, Page<Object, dynamic> page) {
      /// AOP
      /// 页面可以有一些私有的 AOP 的增强, 但往往会有一些 AOP 是整个应用下,所有页面都会有的。
      /// 这些公共的通用 AOP ,通过遍历路由页面的形式统一加入。
      page.enhancer.append(
        /// View AOP
        viewMiddleware: <ViewMiddleware<dynamic>>[
          safetyView<dynamic>(),
        ],

        /// Adapter AOP
        adapterMiddleware: <AdapterMiddleware<dynamic>>[safetyAdapter<dynamic>()],

        /// Effect AOP
        effectMiddleware: [
         // _pageAnalyticsMiddleware<dynamic>(),
        ],

        /// Store AOP
        middleware: <Middleware<dynamic>>[
          //这块主要用到middleware的打印功能,监听Action在页面间的调整过程
          //logMiddleware<dynamic>(tag: page.runtimeType.toString()), //这块主要用到middleware的打印功能,监听Action在页面间的调整过程
        ],
      );
    },
  );

  return MaterialApp(
    title: 'fish_redux_demo',
    debugShowCheckedModeBanner: false,
    theme: ThemeData(
      primarySwatch: Colors.blue,
    ),
    home: routes.buildPage('login',null),
    onGenerateRoute: (RouteSettings settings) {
      return MaterialPageRoute<Object>(builder: (BuildContext context) {
        return routes.buildPage(settings.name, settings.arguments);
      });
    },
  );
}

2.2新建第一个fish_redux页面Login

首先打开AS,新建一个package命名为login,表示登录页面,然后利用插件FishReduxTemplate,弹出快速创建页面:


创建fish_redux的文件目录

创建page需要的依赖.png
2.2.1编写Login-Page.dart,zhuyao
import 'package:fish_redux/fish_redux.dart';

import 'effect.dart';
import 'state.dart';
import 'view.dart';

class LoginPage extends Page<LoginState, Map<String, dynamic>> {
  LoginPage()
      : super(
            initState: initState,
            effect: buildEffect(),
            view: buildView,
           );

}
2.2.2 编写Login-page-state.dart,对应page.dart里面的initState
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/cupertino.dart';

class LoginState implements Cloneable<LoginState> {
  TextEditingController userNameController;
  TextEditingController passwordController;

  @override
  LoginState clone() {
    return LoginState()
    ..userNameController = userNameController
      ..passwordController = passwordController;
  }
}

LoginState initState(Map<String, dynamic> args) {
  LoginState state = LoginState();
  state.userNameController = new TextEditingController();
  state.passwordController = new TextEditingController();
  return state;
}
2.2.3 编写Login-page-view.dart,对应page.dart里面的buildView
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';

import 'action.dart';
import 'state.dart';

Widget buildView(LoginState state, Dispatch dispatch, ViewService viewService) {
  return Scaffold(
    appBar: PreferredSize(
      preferredSize: Size.fromHeight(50.0),
      child: AppBar(
        automaticallyImplyLeading: false,
        centerTitle: true,
        elevation: 0,
        title: Text('登录'),
      ),
    ),
    body: GestureDetector(
      onTap: (){//键盘收起
        FocusScope.of(viewService.context).requestFocus(new FocusNode());
      },
      child: Container(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            SizedBox(height: 100,),
            Text(
              "安全管控",
              style: TextStyle(
                fontSize: 30,
                fontWeight: FontWeight.bold,
              ),
              textAlign: TextAlign.center,
            ),
            SizedBox(height: 100,),
            Row(
              children: <Widget>[
                SizedBox(width: 50,),
                Expanded(
                  child: TextField(
                    controller: state.userNameController,
                    decoration: InputDecoration(
                      hintText: "用户名",
                    ),
                  ),
                ),
                SizedBox(width: 50,),
              ],
            ),
            SizedBox(height: 30,),
            Row(
              children: <Widget>[
                SizedBox(width: 50,),
                Expanded(
                  child: TextField(
                    controller: state.passwordController,
                    decoration: InputDecoration(
                      hintText: "密码",
                    ),
                    obscureText: true,
                  ),
                ),
                SizedBox(width: 50,),
              ],
            ),
            SizedBox(height: 50,),
            RaisedButton(
              color: Colors.blue,
              child: Text(
                '登录',
                style: TextStyle(
                  color: Colors.white,
                ),
              ),
              onPressed: (){
                if(state.userNameController.text.isNotEmpty){
                  if(state.passwordController.text.isNotEmpty){
                    dispatch(LoginActionCreator.onLogin());
                  }
                  else{
                    Fluttertoast.showToast(msg: '密码不能为空');
                  }
                }
                else{
                  Fluttertoast.showToast(msg: '账号不能为空');
                }
              },
            ),
          ],
        ),
      ),
    ),
  );
}
2.2.4 编写Login-page-effect.dart,对应page.dart里面的buildEffect
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/cupertino.dart' hide Action;
import 'package:flutter_redux_demo/network/netuntil.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'action.dart';
import 'state.dart';
import 'dart:convert';
import 'package:convert/convert.dart';
import 'package:crypto/crypto.dart';

Effect<LoginState> buildEffect() {
  return combineEffects(<Object, Effect<LoginState>>{
    LoginAction.login: _onLogin,
  });
}

void _onLogin(Action action, Context<LoginState> ctx) {
  Map<String, dynamic> params = {
    'username':ctx.state.userNameController.text,
    'pwd':generateMd5(ctx.state.passwordController.text),
  };
  //登录接口
  NetUtlis.login('/userright/loginVerify.do',params: params,success: (value){
    if(value == '1'){
      Fluttertoast.showToast(msg: '登录成功');
      Navigator.of(ctx.context).pushNamed('home');
    }
    else{
      Fluttertoast.showToast(msg: '登录失败');
    }

  });
}
//MD5转码
String generateMd5(String data) {
  var content = new Utf8Encoder().convert(data);
  var digest = md5.convert(content);
  // 这里其实就是 digest.toString()
  return hex.encode(digest.bytes);
}
2.2.5 编写Login-page-action.dart,对应effect.dart里面意图action
import 'package:fish_redux/fish_redux.dart';

enum LoginAction { login }

class LoginActionCreator {
  static Action onLogin() {
    return const Action(LoginAction.login);
  }
}

至此,登录页面逻辑基本完成,这里没有用到adapter,dependencies等等.我们进入home页面!

2.3 新建主界面Home,按照上面的插件步骤,建立命名home的package

2.3.1 编写Home-page.dart
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter_redux_demo/home/home_adapter/adapter.dart';

import 'effect.dart';
import 'state.dart';
import 'view.dart';

class HomePage extends Page<HomeState, Map<String, dynamic>> {
  HomePage()
      : super(
            initState: initState,
            effect: buildEffect(),
            view: buildView,
            dependencies: Dependencies<HomeState>(
                adapter: NoneConn<HomeState>() + HomeAdapter(),
                slots: <String, Dependent<HomeState>>{
                }),
            middleware: <Middleware<HomeState>>[
            ],);

}
2.3.2 编写Home-page-state.dart,对应page.dart的initState
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:flutter_redux_demo/network/handover_entity.dart';


class HomeState implements Cloneable<HomeState> {
  EasyRefreshController refreshController;
  List<HandoverDataModel> list;
  int page;
  @override
  HomeState clone() {
    return HomeState()
    ..list = list
    ..refreshController = refreshController
    ..page = page;
  }
}

HomeState initState(Map<String, dynamic> args) {
  HomeState homeState = HomeState();
  homeState.refreshController = new EasyRefreshController();
  homeState.list = new List();
  homeState.page = 1;
  return homeState;
}
2.3.3 编写Home-page.view.dart,对应page.dart里面的buildView
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';

import 'action.dart';
import 'state.dart';

Widget buildView(HomeState state, Dispatch dispatch, ViewService viewService) {
  final ListAdapter adapter = viewService.buildAdapter();
  return Scaffold(
    appBar: PreferredSize(
      preferredSize: Size.fromHeight(50.0),
      child: AppBar(
        automaticallyImplyLeading: false,
        centerTitle: true,
        elevation: 0,
        title: Text('值长日志'),
      ),
    ),
    body: SafeArea(
      child: EasyRefresh(
//        bottomBouncing: false,
//        topBouncing: false,
        header: ClassicalHeader(),
        footer: ClassicalFooter(
          enableInfiniteLoad: false,
        ),
        controller: state.refreshController,
        onRefresh: () async{
          dispatch(HomeActionCreator.onRefresh());
        },
        onLoad: () async{
          dispatch(HomeActionCreator.onLoad(state.page));
        },
        child: ListView.builder(
          itemBuilder: adapter.itemBuilder,
          itemCount: adapter.itemCount,
        ),
      ),
    ),
  );

}
2.3.4 编写Home-page-action.dart,对应view.datr里面用的action
import 'package:fish_redux/fish_redux.dart';

enum HomeAction { onLoad, onRefresh }

class HomeActionCreator {
  static Action onLoad(int page) {
    return  Action(HomeAction.onLoad,payload: page);
  }

  static Action onRefresh(){
    return  Action(HomeAction.onRefresh);
  }
}
2.3.5 编写Home-page-effect.dart,对应page.dart里面的buildEffect
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter_redux_demo/home/home_adapter/action.dart';
import 'package:flutter_redux_demo/network/handover_entity.dart';
import 'package:flutter_redux_demo/network/netuntil.dart';
import 'action.dart';
import 'state.dart';

Effect<HomeState> buildEffect() {
  return combineEffects(<Object, Effect<HomeState>>{
    Lifecycle.initState:_init,
    HomeAction.onLoad: _onLoad,
    HomeAction.onRefresh: _onRefresh,
  });
}

void _init(Action action, Context<HomeState> ctx){
  ctx.dispatch(HomeActionCreator.onRefresh());
}

void _onLoad(Action action, Context<HomeState> ctx) {
  int page = action.payload;
  println("onload$page");
  NetUtlis.get("/utdcjjb/fdjiaojiebanlog/list.do?filter={'gangweiCode_\$equal':'5'}&page=$page&rows=10&sidx=lrtime&sord=desc",success: (value){
    ctx.state.page += 1;
    HandoverEntity entity = HandoverEntity.fromJson(value);
    ctx.state.refreshController.finishLoad(noMore: entity.data.xList.length <10);
    ctx.dispatch(HandOverActionCreator.loadMoreData(entity.data.xList));
  },failure: (error){
    print(error);
  });
}

void _onRefresh(Action action,Context<HomeState> ctx){
  int page = 1;
  ctx.state.page = 1;
  NetUtlis.get("/utdcjjb/fdjiaojiebanlog/list.do?filter={'gangweiCode_\$equal':'5'}&page=$page&rows=10&sidx=lrtime&sord=desc",success: (value){
    ctx.state.page += 1;
    HandoverEntity entity = HandoverEntity.fromJson(value);
    ctx.state.refreshController.resetLoadState();
    ctx.state.refreshController.finishRefresh();
    ctx.dispatch(HandOverActionCreator.reloadData(entity.data.xList));
  },failure: (error){
    print(error);
  });
}
2.3.6 新建一个home_adapter的package,按照插件格式,生成文件目录,编写Home-adapter.dart,对应page.dart里面dependencies的adapter
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter_redux_demo/home/home_component/component.dart';
import 'package:flutter_redux_demo/home/home_component/state.dart';
import 'package:flutter_redux_demo/network/handover_entity.dart';

import 'reducer.dart';
import '../state.dart';

class HomeAdapter extends DynamicFlowAdapter<HomeState> {
  HomeAdapter()
      : super(
          pool: <String, Component<Object>>{
            'handover': HandOverComponent(),
          },
          connector: _HomeConnector(),
          reducer: buildReducer(),
        );
}

class _HomeConnector extends ConnOp<HomeState, List<ItemBean>> {
  @override
  List<ItemBean> get(HomeState state) {
    if(state.list?.isNotEmpty == true){
      return state.list.map<ItemBean>((m){
        HandOverState handOverState = new HandOverState();
        handOverState.handoverDataModel = m;
            return ItemBean("handover", handOverState);
      }).toList();
    }else{
      return<ItemBean>[];
    }

  }

  @override
  void set(HomeState state, List<ItemBean> items) {
    if(items.isNotEmpty == true){
      state.list = List<HandoverDataModel>.from(items.map((m){
        return m;
    }).toList());
  }else{
      state.list = new List();
    }
  }
}
2.3.7 编写adapter的的action.dart和reducer.dart,生命周期比较长
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter_redux_demo/network/handover_entity.dart';
enum HandOverAction { reloadData, loadMoreData }

class HandOverActionCreator {
  static Action reloadData(List<HandoverDataModel> list) {
    return Action(HandOverAction.reloadData,payload: list);
  }

  static Action loadMoreData(List<HandoverDataModel> list) {
    return Action(HandOverAction.loadMoreData,payload: list);
  }
}
import 'package:fish_redux/fish_redux.dart';
import 'action.dart';
import '../state.dart';

Reducer<HomeState> buildReducer() {
  return asReducer(
    <Object, Reducer<HomeState>>{
      HandOverAction.reloadData: _reloadData,
      HandOverAction.loadMoreData:_loadMoreData,
    },
  );
}
//更新数据
HomeState _reloadData(HomeState state, Action action) {
  final HomeState newState = state.clone();
//  newState.list.clear();
  newState.list = action.payload;
  return newState;
}

HomeState _loadMoreData(HomeState state, Action action){
  final HomeState newState = state.clone();
  newState.list.addAll(action.payload);
  return newState;
}
2.3.8 新建home_component的package,根据插件提示生成目录结构。编写Home-page-component.dart
import 'package:fish_redux/fish_redux.dart';

import 'effect.dart';
import 'state.dart';
import 'view.dart';

class HandOverComponent extends Component<HandOverState> {
  HandOverComponent()
      : super(
          view: buildView,
        );
}
2.3.9 编写Home-page-component-state.dart,保存item的数据
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter_redux_demo/network/handover_entity.dart';

class HandOverState implements Cloneable<HandOverState> {
  HandoverDataModel handoverDataModel;
  @override
  HandOverState clone() {
    return HandOverState()..handoverDataModel = handoverDataModel;
  }
}
2.3.10 因为目前component只有buildView,所以我们只需要编写Home-page-component-view.dart!如果item点击需要进入详情等,就需要编写effect,reducer,action等等
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/material.dart';
import 'package:flustars/flustars.dart';
import 'state.dart';

Widget buildView(HandOverState state, Dispatch dispatch, ViewService viewService) {
  return Container(
    margin: EdgeInsets.fromLTRB(8, 4, 8, 4),
    decoration: BoxDecoration(
      border: Border.all(
        color: Color(0xffe8ebf0),
        width: 1,
      ),
      color: Colors.white,
    ),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        Padding(padding: EdgeInsets.only(top: 5)),
        Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Padding(padding: EdgeInsets.only(left: 16),),
            Text(
              (TextUtil.isEmpty(state.handoverDataModel.zhiciFname)?"当前班" : state.handoverDataModel.zhiciFname) + '-' + state.handoverDataModel.banciFname,

              style: TextStyle(
                fontSize: 16,
                fontWeight: FontWeight.bold,
                color: Colors.black,
              ),
            ),
            Spacer(
              flex: 1,
            ),
            Text(
              state.handoverDataModel.statusconfirm == "1"
                  ? "已接班"
                  : "未接班",
              style: TextStyle(
                fontSize: 14,
                color: state.handoverDataModel.statusconfirm == "1"
                    ? Color(0xFF00CA8D)
                    : Color(0xFFFF9B26),
              ),
            ),
            Padding(padding: EdgeInsets.only(right: 16),),
          ],
        ),
        Padding(padding: EdgeInsets.only(top: 2)),
        Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Padding(padding: EdgeInsets.only(left: 16),),
            Text(
              "当班值长 :" + state.handoverDataModel.dbpeople,
              style: TextStyle(
                fontSize: 14,
                color: Color(0xFF676767),
              ),
            ),
          ],
        ),
        Padding(padding: EdgeInsets.only(top: 2)),
        Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Padding(padding: EdgeInsets.only(left: 16),),
            Text(
              "接班时间 :" + state.handoverDataModel.mdate +"  " +
                  state.handoverDataModel.jbtime,
              style: TextStyle(
                fontSize: 14,
                color: Color(0xFF676767),
              ),
            )
          ],
        ),
        Padding(padding: EdgeInsets.only(bottom: 5)),
      ],
    ),
  );
}

至此,Home的页面也编写完成,逻辑也串起来了!
让我们来看看预览画面

登录界面.png

Home界面.png

3. fish_redux总结和参考资料

fish_redux中文文档

手把手入门Fish-Redux开发flutter

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270