<p>很久没在 SegmentFault 写东西,唉,也不知道 是忙仍是懒,之后有时间 再慢慢写起来吧,最近开始学点新东西,有的写了,我的博客跟这里同步。</p> <p>一直都在本身的 React Native 应用中使用 Redux,其实更大状况下也是使用它来管理应用的会话状态以及当前登陆的用户信息等等简单的数据,很好用,自从 Google 发布 Flutter 以后,就一直想着拿它来作点啥,准备拿一个新项目开刀,先研究下怎么把之前在 React Native 中须要用到的一些技术在 Flutter 找到对应的实现方法,本文记录下 Flutter + Redux + Redux Persist 的实现。</p> <p>原文地址:<a href="https://pantao.onmr.com/press/flutter-redux-persist-app.html" rel="nofollow noreferrer">Flutter + Redux + Redux Persist 应用</a><br>项目地址:<a href="https://github.com/pantao/flutter-redux-demo-app" rel="nofollow noreferrer">https://github.com/pantao/flutter-redux-demo-app</a></p> <p><!--more--></p> <h2>第一步:建立一个新的应用:<code>redux_demo_app</code> </h2> ```flutter create redux_demo_app cd redux_demo_app code . ```html
<blockquote>Flutter 项目必须是一个合法的 Dart 包,而 Dart 包要求使用纯小写字母(可包含下划线),这个跟 React Native 是不同的。</blockquote> <h2>第二步:添加依懒</h2> <p>咱们依懒下面这些包:</p> <ul> <li> <a href="https://pub.dartlang.org/packages/redux" rel="nofollow noreferrer">Redux</a> : JavaScript Redux 的复刻版</li> <li> <a href="https://pub.dartlang.org/packages/flutter_redux" rel="nofollow noreferrer">Flutter Redux</a>:相似于 React Redux 同样,让咱们在 Flutter 项目中更好的使用 Redux</li> <li> <a href="https://pub.dartlang.org/packages/redux_persist" rel="nofollow noreferrer">Redux Persist</a>:Redux 持久化</li> <li> <a href="https://pub.dartlang.org/packages/redux_persist_flutter" rel="nofollow noreferrer">Redux Persist Flutter</a>:Flutter Redux Persist 引擎</li> </ul> <p>打开 <code>pubspec.yaml</code>,在 <code>dependencies</code> 中添加下面这些依懒:</p>react
.. dependencies: ... redux: ^3.0.0 flutter_redux: ^0.5.2 redux_persist: ^0.8.0 redux_persist_flutter: ^0.8.0 dev_dependencies: ... ...
<h2>第三步:了解需求</h2> <p>本次我想作的一个App有下面四个页面:</p> <ul> <li>首页</li> <li>我的中心页</li> <li>我的资料详情页</li> <li>登陆页</li> </ul> <p>交互是下面这样的:</p> <ul> <li>应用打开以后,打开的是一个有两个底部 Tab 的应用,默认展现的是首页</li> <li> <p>当用户点击(个人)这个Tab时:</p> <ul> <li>若当前用户已登陆,则Tab切换为我的中心页</li> <li>若当前用户未登陆,则以 Modal 的方式弹出登陆页</li> </ul> </li> </ul> <h2>添加 <code>lib/state.dart</code> 文件</h2> <p>内容以下:</p>git
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; } }
<p>在上面的代码中,咱们先声明了 <code>Actions</code> 枚举,以及一个 <code>AppState</code> 类,该类就是咱们的应用状态类,使用 <code>_authorizationToken</code> 保证认证的值不可被实例外直接被访问到,这样用户就没法去直接修改它的值,再提供了两个 <code>get</code> 方法,提供给外部访问它的值。</p> <p>接着咱们定义了一个 <code>reducer</code> 函数,用于更新状态。</p> <h2>建立 <code>app.dart</code> </h2>github
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() ), ); } }
<p>在上面咱们已经完成的 <code>App</code> 类的编码,如今须要完成 <code>Root</code> 页,也就是咱们的App入口页。</p> <h2>建立 <code>Root</code> 页</h2>json
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('个人') ) ], ), ); }, ); } }
<h2>建立 <code>Home</code> </h2> <p>与 <code>Root</code> 页面相似,咱们能够在任何页面方便的使用 <code>AppState</code></p>redux
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 ) ); }, ) ), ); }, ); } }
<h2>完成 <code>Auth</code> </h2> <p>在前面的全部页面中,都只是对 <code>store</code> 中状态树的读取,如今的 <code>Auth</code> 就须要完成对状态树的更新了,看下面代码:</p>segmentfault
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); }, ) ), ); }, ); } }
<h2>建立 <code>Me</code> </h2> <p>有了登陆以后,咱们能够在作一个个人页面,在这个页面里面咱们能够完成退出功能。</p>app
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将自动切换首页 }, ) ), ); }, ); } }
<h2>添加状态持久化</h2> <p>在上面,咱们已经完成了一个基于 Redux 的同步状态的App,可是当你的App关闭从新打开以外,状态树就会被重置为初始值,这并不理想,咱们常常须要一个用户完成登陆以后,就能够在一断时间内一直保持这个登陆状态,并且有一些数据咱们并不但愿每次打开App的时候都从新初始化一次,这个时候,能够考虑对状态进行持久化了。</p> <h3>更新 <code>state.dart</code> </h3>less
class AppState { ... // 持久化时,从 JSON 中初始化新的状态 static AppState fromJson(dynamic json) => json != null ? AppState(json['authorizationToken'] as String) : AppState(''); // 更新状态以后,转成 JSON,而后持久化至持久化引擎中 dynamic toJson() => {'authorizationToken': _authorizationToken}; }
<p>这里咱们添加了两个方法,一个是静态的 <code>fromJson</code> 方法,它将在初始化状态树时被调用,用于从 JSON 中初始化一个新的状态树出来, <code>toJson</code> 将被用于持久化,将自身转成 JSON。</p> <h3>更新 <code>main.dart</code> </h3>async
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)); }
<p>从新 <code>flutter run</code> 当前应用,即完成了持久化,能够登陆,而后退出应用,再从新打开应用,能够看到上一次的登陆状态是存在的。</p>