很久没在 SegmentFault 写东西,唉,也不知道 是忙仍是懒,之后有时间 再慢慢写起来吧,最近开始学点新东西,有的写了,我的博客跟这里同步。html
一直都在本身的 React Native 应用中使用 Redux,其实更大状况下也是使用它来管理应用的会话状态以及当前登陆的用户信息等等简单的数据,很好用,自从 Google 发布 Flutter 以后,就一直想着拿它来作点啥,准备拿一个新项目开刀,先研究下怎么把之前在 React Native 中须要用到的一些技术在 Flutter 找到对应的实现方法,本文记录下 Flutter + Redux + Redux Persist 的实现。react
原文地址:Flutter + Redux + Redux Persist 应用
项目地址:https://github.com/pantao/flutter-redux-demo-appgit
<!--more-->github
redux_demo_app
flutter create redux_demo_app cd redux_demo_app code .
json
Flutter 项目必须是一个合法的 Dart 包,而 Dart 包要求使用纯小写字母(可包含下划线),这个跟 React Native 是不同的。
咱们依懒下面这些包:redux
打开 pubspec.yaml
,在 dependencies
中添加下面这些依懒:segmentfault
.. dependencies: ... redux: ^3.0.0 flutter_redux: ^0.5.2 redux_persist: ^0.8.0 redux_persist_flutter: ^0.8.0 dev_dependencies: ... ...
本次我想作的一个App有下面四个页面:app
交互是下面这样的:less
当用户点击(个人)这个Tab时:async
lib/state.dart
文件内容以下:
enum Actions{ login, logout } /// App 状态 /// /// 状态中全部数据都应该是只读的,因此,所有以 get 的方式提供对外访问,不提供 set 方法 class AppState { /// J.W.T String _authorizationToken; // 获取当前的认证 Token get authorizationToken => _authorizationToken; // 获取当前是否处于已认证状态 get authed => _authorizationToken.length > 0; AppState(this._authorizationToken); } /// Reducer AppState reducer(AppState state, action) { switch(action) { case Actions.login: return AppState('J.W.T'); case Actions.logout: return AppState(''); default: return state; } }
在上面的代码中,咱们先声明了 Actions
枚举,以及一个 AppState
类,该类就是咱们的应用状态类,使用 _authorizationToken
保证认证的值不可被实例外直接被访问到,这样用户就没法去直接修改它的值,再提供了两个 get
方法,提供给外部访问它的值。
接着咱们定义了一个 reducer
函数,用于更新状态。
app.dart
import 'package:flutter/material.dart'; import 'package:redux/redux.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'state.dart'; import 'root.dart'; /// 示例App class DemoApp extends StatelessWidget { // app store final Store<AppState> store; DemoApp(this.store); @override Widget build(BuildContext context) { return StoreProvider<AppState>( store: store, child: new MaterialApp( title: 'Flutter Redux Demo App', // home 为 root 页 home: Root() ), ); } }
在上面咱们已经完成的 App
类的编码,如今须要完成 Root
页,也就是咱们的App入口页。
Root
页import 'package:flutter/material.dart'; import 'package:redux/redux.dart'; import 'package:flutter_redux/flutter_redux.dart'; /// 状态 import 'state.dart'; /// 登陆页面 import 'auth.dart'; /// 个人页面 import 'me.dart'; /// 首页 import 'home.dart'; /// 应用入口页 class Root extends StatefulWidget { @override State<StatefulWidget> createState() { return _RootState(); } } /// 入口页状态 class _RootState extends State<Root> { /// 当前被激活的 Tab Index int _currentTabIndex; /// 全部 Tab 列表页 List<Widget> _tabPages; @override void initState() { super.initState(); // 初始化 tab 为第 0 个 _currentTabIndex = 0; // 初始化页面列表 _tabPages = <Widget>[ // 首页 Home(), // 个人 Me() ]; } @override Widget build(BuildContext context) { // 使用 StoreConnector 建立 Widget // 相似于 React Redux 的 connect,连接 store state 与 Widget return StoreConnector<AppState, Store<AppState>>( // store 转换器,相似于 react redux 中的 mapStateToProps 方法 // 接受参数为 `store`,再返回的数据能够被在 `builder` 函数中使用, // 在此处,咱们直接返回整个 store, converter: (store) => store, // 构建器,第二个参数 store 就是上一个 converter 函数返回的 store builder: (context, store) { // 取得当前是否已登陆状态 final authed = store.state.authed; return new Scaffold( // 若是已登陆,则直接能够访问全部页面,不然展现 Home body: authed ? _tabPages[_currentTabIndex] : Home(), // 底部Tab航 bottomNavigationBar: BottomNavigationBar( onTap: (int index) { // 若是点击的是第 1 个Tab,且当前用户未登陆,则直接打开登陆 Modal 页 if (!authed && index == 1) { Navigator.push( context, MaterialPageRoute( builder: (context) => Auth(), fullscreenDialog: true ) ); // 不然直接进入相应页面 } else { setState(() { _currentTabIndex = index; }); } }, // 与 body 取值方式相似 currentIndex: authed ? _currentTabIndex : 0, items: [ BottomNavigationBarItem( icon: Icon(Icons.home), title: Text('首页') ), BottomNavigationBarItem( icon: Icon(Icons.people), title: Text('个人') ) ], ), ); }, ); } }
Home
与 Root
页面相似,咱们能够在任何页面方便的使用 AppState
import 'package:flutter/material.dart'; import 'package:redux/redux.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'state.dart'; import 'auth.dart'; class Home extends StatefulWidget { @override State<StatefulWidget> createState() => _HomeState(); } class _HomeState extends State<Home> { @override Widget build(BuildContext context) { return StoreConnector<AppState, Store<AppState>>( converter: (store) => store, builder: (context, store) { return Scaffold( appBar: AppBar( title: Text('首页'), ), body: Center( child: store.state.authed ? Text('您已登陆') : FlatButton( child: Text('去登陆'), onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => Auth(), fullscreenDialog: true ) ); }, ) ), ); }, ); } }
Auth
在前面的全部页面中,都只是对 store
中状态树的读取,如今的 Auth
就须要完成对状态树的更新了,看下面代码:
import 'package:flutter/material.dart'; import 'package:redux/redux.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'state.dart'; class Auth extends StatefulWidget { @override State<StatefulWidget> createState() => _AuthState(); } class _AuthState extends State<Auth> { @override Widget build(BuildContext context) { return StoreConnector<AppState, Store<AppState>>( converter: (store) => store, builder: (context, store) { return Scaffold( appBar: AppBar( title: Text('登陆'), ), body: Center( child: FlatButton( child: Text('登陆'), onPressed: () { // 经过 store.dispatch 函数,能够发出 action(跟 Redux 是同样的),而 Action 是在 // AppState 中定义的枚举 Actions.login store.dispatch(Actions.login); // 以后,关闭当前的 Modal,就能够看到应用全部数据都更新了 Navigator.pop(context); }, ) ), ); }, ); } }
Me
有了登陆以后,咱们能够在作一个个人页面,在这个页面里面咱们能够完成退出功能。
import 'package:flutter/material.dart'; import 'package:redux/redux.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'state.dart'; class Me extends StatefulWidget { @override State<StatefulWidget> createState() => _MeState(); } class _MeState extends State<Me> { @override Widget build(BuildContext context) { return StoreConnector<AppState, Store<AppState>>( converter: (store) => store, builder: (context, store) { return Scaffold( appBar: AppBar( title: Text('退出'), ), body: Center( child: FlatButton( child: Text('退出'), onPressed: () { store.dispatch(Actions.logout); // 此处咱们不须要去更新Tab Index,在 Root 页面中,对 store 里面的 authed 值已经作了监听,若是 // Actions.logout 被触发后, authed 的值会变成 false,那么App将自动切换首页 }, ) ), ); }, ); } }
在上面,咱们已经完成了一个基于 Redux 的同步状态的App,可是当你的App关闭从新打开以外,状态树就会被重置为初始值,这并不理想,咱们常常须要一个用户完成登陆以后,就能够在一断时间内一直保持这个登陆状态,并且有一些数据咱们并不但愿每次打开App的时候都从新初始化一次,这个时候,能够考虑对状态进行持久化了。
state.dart
class AppState { ... // 持久化时,从 JSON 中初始化新的状态 static AppState fromJson(dynamic json) => json != null ? AppState(json['authorizationToken'] as String) : AppState(''); // 更新状态以后,转成 JSON,而后持久化至持久化引擎中 dynamic toJson() => {'authorizationToken': _authorizationToken}; }
这里咱们添加了两个方法,一个是静态的 fromJson
方法,它将在初始化状态树时被调用,用于从 JSON 中初始化一个新的状态树出来, toJson
将被用于持久化,将自身转成 JSON。
main.dart
import 'package:flutter/material.dart'; import 'package:redux/redux.dart'; import 'package:redux_persist/redux_persist.dart'; import 'package:redux_persist_flutter/redux_persist_flutter.dart'; import 'app.dart'; import 'state.dart'; void main() async { // 建立一个持久化器 final persistor = Persistor<AppState>( storage: FlutterStorage(), serializer: JsonSerializer<AppState>(AppState.fromJson), debug: true ); // 从 persistor 中加载上一次存储的状态 final initialState = await persistor.load(); final store = Store<AppState>( reducer, initialState: initialState ?? AppState(''), middleware: [persistor.createMiddleware()] ); runApp(new DemoApp(store)); }
从新 flutter run
当前应用,即完成了持久化,能够登陆,而后退出应用,再从新打开应用,能够看到上一次的登陆状态是存在的。
来源:https://segmentfault.com/a/1190000017405058