<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>