基于 Redux + Redux Persist 进行状态管理的 Flutter 应用示例

简介: 好久没在 SegmentFault 写东西,唉,也不知道 是忙还是懒,以后有时间 再慢慢写起来吧,最近开始学点新东西,有的写了,个人博客跟这里同步。

好久没在 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 是不一样的。

第二步:添加依懒

我们依懒下面这些包:

打开 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 =&gt; _authorizationToken;

  // 获取当前是否处于已认证状态
  get authed =&gt; _authorizationToken.length &gt; 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&lt;AppState&gt; store;

  DemoApp(this.store);

  @override
  Widget build(BuildContext context) {
    return StoreProvider&lt;AppState&gt;(
      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&lt;StatefulWidget&gt; createState() {
      return _RootState();
    }
}

/// 入口页状态
class _RootState extends State&lt;Root&gt; {
  /// 当前被激活的 Tab Index
  int _currentTabIndex;
  /// 所有 Tab 列表页
  List&lt;Widget&gt; _tabPages;

  @override
  void initState() {
    super.initState();
    // 初始化 tab 为第 0 个
    _currentTabIndex = 0;
    // 初始化页面列表
    _tabPages = &lt;Widget&gt;[
      // 首页
      Home(),
      // 我的
      Me()
    ];
  }

  @override
  Widget build(BuildContext context) {
    // 使用 StoreConnector 创建 Widget
    // 类似于 React Redux  的 connect,链接 store state 与 Widget
    return StoreConnector&lt;AppState, Store&lt;AppState&gt;&gt;(
      // store 转换器,类似于 react redux 中的 mapStateToProps 方法
      // 接受参数为 `store`,再返回的数据可以被在 `builder` 函数中使用,
      // 在此处,我们直接返回整个 store,
      converter: (store) =&gt; 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 &amp;&amp; index == 1) {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) =&gt; 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&lt;StatefulWidget&gt; createState() =&gt; _HomeState();
}

class _HomeState extends State&lt;Home&gt; {
  @override
  Widget build(BuildContext context) {
    return StoreConnector&lt;AppState, Store&lt;AppState&gt;&gt;(
      converter: (store) =&gt; 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) =&gt; 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&lt;StatefulWidget&gt; createState() =&gt; _AuthState();
}

class _AuthState extends State&lt;Auth&gt; {
  @override
  Widget build(BuildContext context) {
    return StoreConnector&lt;AppState, Store&lt;AppState&gt;&gt;(
      converter: (store) =&gt; 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&lt;StatefulWidget&gt; createState() =&gt; _MeState();
}

class _MeState extends State&lt;Me&gt; {
  @override
  Widget build(BuildContext context) {
    return StoreConnector&lt;AppState, Store&lt;AppState&gt;&gt;(
      converter: (store) =&gt; 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) =&gt; json != null ? AppState(json['authorizationToken'] as String) : AppState('');

  // 更新状态之后,转成 JSON,然后持久化至持久化引擎中
  dynamic toJson() =&gt; {'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&lt;AppState&gt;(
    storage: FlutterStorage(),
    serializer: JsonSerializer&lt;AppState&gt;(AppState.fromJson),
    debug: true
  );

  // 从 persistor 中加载上一次存储的状态
  final initialState = await persistor.load();

  final store = Store&lt;AppState&gt;(
    reducer,
    initialState: initialState ?? AppState(''),
    middleware: [persistor.createMiddleware()]
  );
  runApp(new DemoApp(store));
}

重新 flutter run 当前应用,即完成了持久化,可以登录,然后退出应用,再重新打开应用,可以看到上一次的登录状态是存在的。

来源:https://segmentfault.com/a/1190000017405058

相关文章
|
10天前
|
缓存 监控 前端开发
【Flutter 前端技术开发专栏】Flutter 应用的启动优化策略
【4月更文挑战第30天】本文探讨了Flutter应用启动优化策略,包括理解启动过程、资源加载优化、减少初始化工作、界面布局简化、异步初始化、预加载关键数据、性能监控分析以及案例和未来优化方向。通过这些方法,可以缩短启动时间,提升用户体验。使用Flutter DevTools等工具可助于识别和解决性能瓶颈,实现持续优化。
【Flutter 前端技术开发专栏】Flutter 应用的启动优化策略
|
2月前
|
iOS开发 UED
Flutter 动态修改应用图标功能指南
探索Flutter中动态应用图标的实现方法,了解如何为用户提供独特体验,促进用户升级和应用内购买。
100 0
Flutter 动态修改应用图标功能指南
|
3月前
|
存储 JavaScript 前端开发
盘点主流 Flutter 状态管理库2024
状态管理是每个应用不可缺少的,本文将会盘点下主流的状态管理包。
盘点主流 Flutter 状态管理库2024
|
10天前
|
Dart 前端开发 开发工具
【Flutter前端技术开发专栏】Flutter入门指南:搭建开发环境与第一个应用
【4月更文挑战第30天】本文介绍了Flutter SDK的安装和配置过程,以及如何创建并运行第一个Flutter应用。首先确保安装了Dart SDK和Flutter SDK,支持macOS、Linux和Windows。安装完成后,设置环境变量,然后通过`flutter doctor`验证安装。接着,使用`flutter create`命令创建新项目,进入项目目录并运行`flutter run`启动应用。在`main.dart`中修改代码以自定义应用。Flutter支持热重载和DevTools调试。本文为Flutter初学者提供了快速入门的指导。
【Flutter前端技术开发专栏】Flutter入门指南:搭建开发环境与第一个应用
|
10天前
|
缓存 监控 前端开发
【Flutter前端技术开发专栏】Flutter应用的性能调优与测试
【4月更文挑战第30天】本文探讨了Flutter应用的性能调优策略和测试方法。性能调优对提升用户体验、降低能耗和增强稳定性至关重要。优化布局(避免复杂嵌套,使用`const`构造函数)、管理内存、优化动画、实现懒加载和按需加载,以及利用Flutter的性能工具(如DevTools)都是有效的调优手段。性能测试包括基准测试、性能分析、压力测试和电池效率测试。文中还以ListView为例,展示了如何实践这些优化技巧。持续的性能调优是提升Flutter应用质量的关键。
【Flutter前端技术开发专栏】Flutter应用的性能调优与测试
|
10天前
|
前端开发 Android开发 开发者
【Flutter前端技术开发专栏】Flutter中的混合应用(Hybrid Apps)开发
【4月更文挑战第30天】本文探讨了使用Flutter开发混合应用的方法。混合应用结合Web技术和原生容器,提供快速开发和低成本维护。Flutter,一款现代前端框架,以其插件系统和高性能渲染引擎支持混合应用开发。通过创建Flutter项目、添加平台代码、使用WebView、处理平台间通信以及发布应用,开发者可构建跨平台混合应用。虽然混合应用有性能和用户体验的局限,但Flutter的跨平台兼容性和丰富的插件生态降低了开发成本。开发者应根据项目需求权衡选择。
【Flutter前端技术开发专栏】Flutter中的混合应用(Hybrid Apps)开发
|
10天前
|
开发框架 Dart 前端开发
【Flutter前端技术开发专栏】Flutter中的Web支持:构建跨平台Web应用
【4月更文挑战第30天】Flutter,Google的开源跨平台框架,已延伸至Web领域,让开发者能用同一代码库构建移动和Web应用。Flutter Web通过将Dart代码编译成JavaScript和WASM运行在Web上。尽管性能可能不及原生Web应用,但适合交互性强、UI复杂的应用。开发者应关注性能优化、兼容性测试,并利用Flutter的声明式UI、热重载等优势。随着其发展,Flutter Web为跨平台开发带来更多潜力。
【Flutter前端技术开发专栏】Flutter中的Web支持:构建跨平台Web应用
|
10天前
|
前端开发 搜索推荐 UED
【Flutter前端技术开发专栏】Flutter中的高级UI组件应用
【4月更文挑战第30天】探索Flutter的高级UI组件,如`TabBar`、`Drawer`、`BottomSheet`,提升应用体验和美观度。使用高级组件能节省开发时间,提供内置交互逻辑和优秀视觉效果。示例代码展示了如何实现底部导航栏、侧边导航和底部弹出菜单。同时,自定义组件允许个性化设计和功能扩展,但也带来性能优化和维护挑战。参考Flutter官方文档和教程,深入学习并有效利用这些组件。
【Flutter前端技术开发专栏】Flutter中的高级UI组件应用
|
15天前
|
存储 自然语言处理 API
Flutter应用的国际化支持:实现多语言环境的优雅策略
【4月更文挑战第26天】Flutter提供强大的国际化(i18n)和本地化(l10n)支持,使开发者能轻松实现应用多语言特性。通过定义`.arb`文件来管理字符串资源,使用`LocalizationsDelegate`加载资源,设置应用语言环境,以及在UI中使用`S.of(context).someString`访问字符串。进阶技巧包括字符串格式化、复数形式、双向文本和Unicode支持。充分测试确保所有语言正确显示。随着全球化需求增长,Flutter的国际化支持成为应用开发关键。
|
15天前
|
存储 UED 开发者
Flutter的状态管理:setState、Provider、Bloc的使用详解
【4月更文挑战第26天】Flutter状态管理详解:涵盖setState基础,Provider的跨组件共享及Bloc的复杂场景处理。了解这三种方法的优缺点,助力优化应用数据一致性与用户体验。当状态管理需求升级,从简单的setState到Provider的便利,再到Bloc的强大功能,开发者可根据项目规模和复杂度选择合适策略。