经过不懈的软磨硬泡以及各种安利…cto终于对Flutter动心了,项目2.15版本将会接入Flutter模块,?真的是喜大普奔…
考虑到未来的业务拓展,也是为了打好一个基础,我对接下来的flutter module进行了框架选型(其实是为了进一步安利,毕竟原生代码真的写得好死鬼烦啊…)。
其实目前flutter也没有什么特别好的框架,所谓框架,不太像android那样的mvc,mvp,mvvm那么成熟,更多的说的是状态管理。
目前flutter成熟的状态管理也就三(si)种:
我们简单介绍下:
FishRedux的gayhub地址为:FishRedux
我们clone项目,大致看下目录结构:
除了通用的global_store之外,页面大致分为三种类型:
官网上介绍,page(页面)是一个行为丰富的组件,因为它的实现是在组件(component)的基础上增强了aop能力,以及自有的state。
component也有自己的state,但是对比起来,page的具备了**initState()**方法而component没有。
比如我们后续的登录页面,我们暂且贴上代码,后面再做具体说明:
login_quick_component代码
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter_module/page/dialog/component.dart';
import 'effect.dart';
import 'reducer.dart';
import 'state.dart';
import 'view.dart';
class LoginQuickComponent extends Component {
LoginQuickComponent()
: super(
effect: buildEffect(),
reducer: buildReducer(),
view: buildView,
dependencies: Dependencies(
adapter: null,
slots: >{
'dialog': DialogConner() + CommDialogComponent()
}),
);
}
login_page代码
import 'package:fish_redux/fish_redux.dart';
import 'StateWithTickerProvider.dart';
import 'effect.dart';
import 'login_quick/component.dart';
import 'reducer.dart';
import 'state.dart';
import 'view.dart';
class LoginPage extends Page> {
@override
StateWithTickerProvider createState() => StateWithTickerProvider();
LoginPage()
: super(
initState: initState,
effect: buildEffect(),
reducer: buildReducer(),
view: buildView,
dependencies: Dependencies(
adapter: null,
slots: >{
"login_quick": QuickConnector() + LoginQuickComponent(),
// "login_normal": QuickConnector() + LoginQuickComponent(),
}),
middleware: >[],
);
}
*组件(Component)*是 Fish Redux 最基本的元素,其实page也是基于Component的,它与page的不同点除了:
/(ㄒoㄒ)/~~ 哈哈哈,我不要你觉得,我要我觉得!!!!
看到这里是不是有点泪流满面了,通篇不知所云,好不容易看到了一个亲切的单词了…
然而…名字叫适配器,但是和android的用法还是有区别的。
不过这个我也是在摸索使用中。
犹豫不决总是梦,其实上面说了那么多,我都不知道我在说啥…我们还是直接看代码吧。
这个是我们具体实现的登录页面,我用安卓的行话讲就是:
上面一个imageView,下面弄了一个tabLayout,里面丢了两个菜单类型:快速登录和密码登录,最底下丢了一个viewpager,里面丢了两个fragment。
简简单单…
然而,flutter的代码结构为:
其实这块是照抄demo的,是一个实现切换主题色的小功能,真爽啊!!!,这块可以略过不讲,很容易看懂。
是页面创建的根,主要用途有:
一个page(component)我们可以看到是由:
action,effect,page(component),reducer,state,view这几个模块组成的,他们分别的作用,我们先稍微了解下,以便后续的代码讲解:
用来定义在这个页面中发生的动作,例如:登录,清理输入框,更换验证码框等。
同时可以通过payload参数传值,传递一些不能通过state传递的值。
这个dart文件在fish_redux中是定义来处理副作用操作的,比如显示弹窗,网络请求,数据库查询等操作。
这个dart文件在用来在路由注册,同时完成注册effect,reducer,component,adapter的功能。
这个dart文件是用来更新View,即直接操作View状态。
state用来定义页面中的数据,用来保存页面状态和数据。
view很明显,就是flutter里面当中展示给用户看到的页面。
反正我第一次看是头晕脑胀的…怎么这么多东西,想我年少时,一个.xml和一个.java走天下。
在这里建议下和我一个弄个记事本,把上面这块抄上去,写的时候忘记了就看看。
由上面的截图可以看出,登录页面由一个page加一个component组成。
我们先逐个逐个看代码,逐个逐个说:
数据先行,我们看下state类,这里比较重要的就是QuickConnector连接器了,等到我们讲login_quick的时候再细说:
import 'dart:async';
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_module/global_store/state.dart';
import 'login_quick/state.dart';
class LoginState implements GlobalBaseState, Cloneable {
// tab控制器
TabController tabControllerForLoginType;
// 菜单list
List loginType = [];
// 从缓存拿的账号(可能有用到)
String accountFromCache;
// 倒数文字
String countDownTips;
// 最大倒数时间
int maxCountTime;
// 当前倒数时间
int currCountTime;
@override
LoginState clone() {
return LoginState()
..loginType = loginType
..tabControllerForLoginType = tabControllerForLoginType
..accountFromCache = accountFromCache;
}
@override
Color themeColor;
}
LoginState initState(Map args) {
LoginState state = new LoginState();
state.loginType.add('快速登录');
state.loginType.add('密码登录');
return state;
}
class QuickConnector
extends Reselect2 {
@override
LoginQuickState computed(String sub0, String sub1) {
return LoginQuickState()
..account = sub0
..sendVerificationTips = sub1
..controllerForAccount = TextEditingController()
..controllerForPsd = TextEditingController();
}
@override
String getSub0(LoginState state) {
return state.accountFromCache;
}
@override
String getSub1(LoginState state) {
return state.countDownTips;
}
@override
void set(LoginState state, LoginQuickState subState) {
state.accountFromCache = subState.account;
state.countDownTips = subState.sendVerificationTips;
}
}
这个是用户直接看到的视图文件,我们稍微理下:
其实也是和我们上文说的布局文件类似,
一imageView
一tabBar
一TabBarView
二login_quick Component 而已
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/material.dart';
import 'package:flutter_module/comm/ColorConf.dart';
import 'action.dart';
import 'state.dart';
Widget buildView(LoginState state, Dispatch dispatch, ViewService viewService) {
return Scaffold(
// appBar: AppBar(
// title: Text('Login'),
// ),
body: Column(
children: [
Container(
child: Image.asset('images/img_logintop.webp'),
),
Container(
child: TabBar(
indicatorColor: ColorConf.color18C8A1,
indicatorPadding: EdgeInsets.zero,
controller: state.tabControllerForLoginType,
labelColor: ColorConf.color18C8A1,
indicatorSize: TabBarIndicatorSize.label,
unselectedLabelColor: ColorConf.color9D9D9D,
tabs: state.loginType
.map((e) => Container(
child: Text(
e,
style: TextStyle(fontSize: 14),
),
padding: const EdgeInsets.only(top: 8, bottom: 8),
))
.toList(),
),
),
Divider(
height: 1,
),
Expanded(
child: TabBarView(
children: [
viewService.buildComponent('login_quick'),
viewService.buildComponent('login_quick'),
],
controller: state.tabControllerForLoginType,
),
flex: 3,
)
],
),
);
}
这里其实就是定义操作,我觉得可以有个类比,拿我半吊子的springboot来说,
action就是一个service接口
effect就是一个serviceImpl实现类
reducer就是根据发出来的action进行页面操作。
login_action只定义了一丢丢操作
import 'package:fish_redux/fish_redux.dart';
enum LoginAction { action, update }
class LoginActionCreator {
static Action onAction() {
return const Action(LoginAction.action);
}
static Action onUpdate(String countDownNumber) {
return Action(LoginAction.update, payload: countDownNumber);
}
}
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_module/page/user/login_page/action.dart';
import 'StateWithTickerProvider.dart';
import 'state.dart';
Effect buildEffect() {
return combineEffects(
这个没有什么好说的,因为loginPage也没有什么特别的操作,唯一比较值得注意的是
这里相对多了一个StateWithTickerProvider
这个文件主要是为了给tabController提供TickerProvider,主要注意几个地方
class StateWithTickerProvider extends ComponentState with TickerProviderStateMixin{
}
@override
StateWithTickerProvider createState() => StateWithTickerProvider();
void _onInit(Action action, Context ctx) {
final TickerProvider tickerProvider = ctx.stfState as StateWithTickerProvider;
ctx.state.tabControllerForLoginType =
TabController(length: ctx.state.loginType.length, vsync: tickerProvider);
}
这个页面主要是用于更新view,怎么更新呢?返回一个newState!!
import 'package:fish_redux/fish_redux.dart';
import 'action.dart';
import 'state.dart';
Reducer buildReducer() {
return asReducer(
page文件,在构造方法里面调用init初始化
import 'package:fish_redux/fish_redux.dart';
import 'StateWithTickerProvider.dart';
import 'effect.dart';
import 'login_quick/component.dart';
import 'reducer.dart';
import 'state.dart';
import 'view.dart';
class LoginPage extends Page> {
@override
StateWithTickerProvider createState() => StateWithTickerProvider();
LoginPage()
: super(
initState: initState,
effect: buildEffect(),
reducer: buildReducer(),
view: buildView,
dependencies: Dependencies(
adapter: null,
slots: >{
"login_quick": QuickConnector() + LoginQuickComponent(),
// "login_normal": QuickConnector() + LoginQuickComponent(),
}),
middleware: >[],
);
}
总结这就来了,是不是只有一个想法:乱!混乱!!好乱!!!什么鬼!!!!mmp!!!!!
我们稍微理理,因为接下来就是点击事件,网络请求了。
我们理下思路:
看,你可以画页面了!!!完美!虽然它点了也没反应。
在这里我们稍微讲下难点的,比如说点击登录
#####我们先云coding一下:
话都说到了这里了,你要不要试着按照上面的6个步骤试下,这样体会更深哦,我们贴下代码:
import 'package:fish_redux/fish_redux.dart';
//TODO replace with your own action
enum TestPageAction { action, doLogin, showUserInfo }
class TestPageActionCreator {
static Action onAction() {
return const Action(TestPageAction.action);
}
static Action onDoLogin() {
return const Action(TestPageAction.doLogin);
}
static Action onShowUserInfo() {
return const Action(TestPageAction.showUserInfo);
}
}
import 'package:fish_redux/fish_redux.dart';
import 'action.dart';
import 'state.dart';
Effect buildEffect() {
return combineEffects(
import 'package:fish_redux/fish_redux.dart';
import 'effect.dart';
import 'reducer.dart';
import 'state.dart';
import 'view.dart';
class TestPagePage extends Page> {
TestPagePage()
: super(
initState: initState,
effect: buildEffect(),
reducer: buildReducer(),
view: buildView,
dependencies: Dependencies(
adapter: null,
slots: >{
}),
middleware: >[
],);
}
import 'package:fish_redux/fish_redux.dart';
import 'action.dart';
import 'state.dart';
Reducer buildReducer() {
return asReducer(
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/material.dart';
class TestPageState implements Cloneable {
TextEditingController controllerForAccount, controllerForPsd;
String userInfoStr;
@override
TestPageState clone() {
return TestPageState()
..controllerForPsd = controllerForPsd
..controllerForAccount = controllerForAccount
..userInfoStr = userInfoStr;
}
}
TestPageState initState(Map args) {
return TestPageState()
..controllerForAccount = TextEditingController()
..userInfoStr=""
..controllerForPsd = TextEditingController();
}
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/material.dart';
import 'action.dart';
import 'state.dart';
Widget buildView(
TestPageState state, Dispatch dispatch, ViewService viewService) {
return Scaffold(
appBar: AppBar(
title: Text('测试'),
),
body: Column(
children: [
Text(state.userInfoStr ?? '暂无信息'),
TextField(
controller: state.controllerForAccount,
),
TextField(
controller: state.controllerForAccount,
),
FlatButton(
onPressed: () {
dispatch(TestPageActionCreator.onDoLogin());
},
child: Text('凌宇是个大帅逼'))
],
),
);
}
其实子模块的代码也没有必要贴了,无非就是action多了一点,加了判空,加了实际网络请求而已,对着上面的代码也是一样的。
再然后,
再还有aop,adapter(其实我有用的,但是登录模块咋讲嘛…且学且用吧)等等东西,越学越有趣,闲鱼大佬真厉害。
突如其来的ending…哈哈哈,大半夜的啤酒加歌有点嗨。
说下遇到的两个点比较坑的: