基于 Redux + Redux Persist 进行状态管理的 Flutter 应用示例
好久没在 SegmentFault 写东西,唉,也不知道 是忙还是懒,以后有时间 再慢慢写起来吧,最近开始学点新东西,有的写了,个人博客跟这里同步。
一直都在自己的 React Native 应用中使用 Redux,其实更大情况下也是使用它来管理应用的会话状态以及当前登录的用户信息等等简单的数据,很好用,自从 Google 发布 Flutter 之后,就一直想着拿它来做点啥,准备拿一个新项目开刀,先研究下怎么把以前在 React Native 中需要用到的一些技术在 Flutter 找到对应的实现方法,本文记录下 Flutter + Redux + Redux Persist 的实现。
原文地址:Flutter + Redux + Redux Persist 应用
项目地址:https://github.com/pantao/flutter-redux-demo-app
<!--more-->
第一步:创建一个新的应用:redux_demo_app
```flutter create redux_demo_app
cd redux_demo_app
code .
```
Flutter 项目必须是一个合法的 Dart 包,而 Dart 包要求使用纯小写字母(可包含下划线),这个跟 React Native 是不一样的。
第二步:添加依懒
我们依懒下面这些包:
- Redux : JavaScript Redux 的复刻版
- Flutter Redux:类似于 React Redux 一样,让我们在 Flutter 项目中更好的使用 Redux
- Redux Persist:Redux 持久化
- Redux Persist Flutter:Flutter Redux Persist 引擎
打开 pubspec.yaml
,在 dependencies
中添加下面这些依懒:
..
dependencies:
...
redux: ^3.0.0
flutter_redux: ^0.5.2
redux_persist: ^0.8.0
redux_persist_flutter: ^0.8.0
dev_dependencies:
...
...
第三步:了解需求
本次我想做的一个App有下面四个页面:
- 首页
- 个人中心页
- 个人资料详情页
- 登录页
交互是下面这样的:
- 应用打开之后,打开的是一个有两个底部 Tab 的应用,默认展示的是首页
当用户点击(我的)这个Tab时:
- 若当前用户已登录,则Tab切换为个人中心页
- 若当前用户未登录,则以 Modal 的方式弹出登录页
添加 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
基于 Redux + Redux Persist 进行状态管理的 Flutter 应用示例的更多相关文章
- 基于cookie的用户登录状态管理
cookie是什么 先来花5分钟看完这篇文章:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies 看完上文,相信大家对cookie已经有 ...
- Redux/Mobx/Akita/Vuex对比 - 选择更适合低代码场景的状态管理方案
近期准备开发一个数据分析 SDK,定位是作为数据中台向外输出数据分析能力的载体,前端的功能表现类似低代码平台的各种拖拉拽.作为中台能力的载体,SDK 未来很大概率会需要支持多种视图层框架,比如Vue2 ...
- 微信小程序里使用 Redux 状态管理
微信小程序里使用 Redux 状态管理 前言 前阵子一直在做小程序开发,采用的是官方给的框架 wepy , 如果还不了解的同学可以去他的官网查阅相关资料学习:不得不说的是,这个框架确相比于传统小程序开 ...
- 为管理复杂组件状态困扰?试试 vue 简单状态管理 Store 模式【转】
https://juejin.im/post/5cd50849f265da03a54c3877 在 vue 中,通信有几种形式: 父子组件 emit/on vuex 中共享 state 跨组件 Eve ...
- 微信小程序全局状态管理 wxscv
微信小程序中,数据状态不同页面中不能跨页面同步更新,也就是缺失类似vuex,mobx,redux全局的数据状态管理功能. 有些人移植了这些库,但是毕竟不是微信小程序生态的东西. Tencent也发布了 ...
- Web前端的状态管理
背景 我相信很多朋友跟我一样,初次听到什么 Flux , Redux , Vuex , 状态管理 的时候是一脸懵逼的.因为在外面之前前端大部分开发的时候,根本没有那么多的概念.自从ReactJS火 ...
- Web前端的状态管理(State Management)
背景 我相信很多朋友跟我一样,初次听到什么Flux, Redux, Vuex,状态管理的时候是一脸懵逼的.因为在外面之前前端大部分开发的时候,根本没有那么多的概念.自从ReactJS火爆后,什么Flu ...
- Sentry 开发者贡献指南 - 前端 React Hooks 与虫洞状态管理模式
系列 Sentry 开发者贡献指南 - 前端(ReactJS生态) Sentry 开发者贡献指南 - 后端服务(Python/Go/Rust/NodeJS) 什么是虫洞状态管理模式? 您可以逃脱的最小 ...
- Flutter实战视频-移动电商-24.Provide状态管理基础
24.Provide状态管理基础 Flutter | 状态管理特别篇 —— Provide:https://juejin.im/post/5c6d4b52f265da2dc675b407?tdsour ...
随机推荐
- Python爬虫十六式 - 第三式:Requests的用法
Requests: 让 HTTP 服务人类 学习一时爽,一直学习一直爽 Hello,大家好,我是Connor,一个从无到有的技术小白.今天我们继续来说我们的 Python 爬虫,上一次我们说到了 ...
- jsp+servlet怎么实现文件断点上传下载
我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用. 这次项目的需求: 支持大文件的上传和续传,要求续传支持所有浏览器,包括ie6,ie7,i ...
- 【转载】Save terminal output to a file
To write the output of a command to a file, there are basically 10 commonly used ways. Overview: Ple ...
- Rust:剑指C++
Rust:极富活力和前途的编程语言,剑指C++ 今天开始学习Rust,马上要回去休息了,只贴上一段实例代码,在后续的学习中,会对这种语言进行一个详尽的介绍(学习中....). extern crate ...
- 织梦DedeCms技术资料
Dedecms调用文章发布时间的方法 11-20 样式 ([field:pubdate function='strftime("%m-%d",@me)'/]) May 15, 20 ...
- opengl中相关的计算机图形变换矩阵之:模型视图几何变换
3. 二维变换矩阵 x' a11 a12 a13 x a11x a12y a13z y' = a21 a22 a23 y = a21x a22y a2 ...
- css中如何使用border属性与display属性
border属性介绍 border属性设置元素边框. 边框3个要素如:粗细.线型.颜色. 边框线型属性值说明表如: 属性指 描述 none 定义无边框. hidden 与 "none&quo ...
- Vue v-if以及 v-else 的使用
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- 《Effective Java》读书笔记 - 5.泛型
Chapter 5 Generics Item 23: Don't use raw types in new code 虽然你可以把一个List<String>传给一个List类型(raw ...
- 关于Anaconda的虚拟环境操作
# 1.创建虚拟环境 conda create -n env_name python==版本号 # 2.激活虚拟环境 conda activate env_name # 3.下载相关模块 pip in ...