Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Flutter状态管理

Flutter状态管理

作者头像
flyou
发布于 2020-06-16 07:24:44
发布于 2020-06-16 07:24:44
1.7K00
代码可运行
举报
文章被收录于专栏:flutter开发者flutter开发者
运行总次数:0
代码可运行

在前面的文章中我们学习了Flutter中事件传递的方法,让我们可以在数据流向简单的业务场景中使用InheritedWidget、Notification 或者 EventBus。

但是随着业务逻辑的复杂,面对不同组件与不同页面之间的数据传递如果还使用前面讲到数据传递的方法就会显得异常繁琐,更会让页面的嵌套增多和数据流向的混乱,所以这个时候我们就需要有一种方案来管理我们需要跨界面传递的数据,于是便有了“状态管理”这个概念。

在前端开发中我们都会接触redux ,借助于redux 我们可以很轻松地完成多界面数据维护和获取,在Flutter中也有很多状态管理的第三方库,如Provider、Scoped Mode、flutter_redux、flutter_mobx 、BLoC、fish_redux等。

Provider作为官方推荐的状态管理工具具有使用简单和管理方便的特点,今天我们就先来看下Provider如何使用。

Provider实现原理

在前面的文章中我们学习过InheritedWidget的用法,通过对InheritedWidget的封装,使得Provider允许在 Widget 树中更加灵活地处理和传递数据。

Provider借助于ChangeNotifier实现发布者-订阅者模式。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class ChangeNotifier implements Listenable {
  List listeners=[];
  @override
  void addListener(VoidCallback listener) {
     //添加监听器
     listeners.add(listener);
  }
@override
void removeListener(VoidCallback listener) {
//移除监听器
    listeners.remove(listener);
  }
void notifyListeners() {
//通知所有监听器,触发监听器回调
    listeners.forEach((item)=>item());
  }

  ...
}

具体的细节我们不再具体去探讨,今天就来看看如何使用。

首先,我们假定这样一个场景,第一个界面显示用户的昵称,然后我们在第二个界面修改昵称再返回观察第一个界面的显示情况。

首先我们建立一个用户信息操作类UserInfoModel使它继承ChangeNotifier

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class UserInfoModel with ChangeNotifier {
  String _nickName = "userName";
  // 读方法
Stringget nickName => _nickName;
// 写方法
void updateNickName(String nickName) {
    _nickName=nickName;
    notifyListeners();// 通知听众刷新
  }
}
数据更新

可以看到我们在UserInfoModel中定义了_nickName属性并设置相关获取与设置属性的方法,在设置属性方法中我们通过notifyListeners方法告知数据刷新。

因为Provider 是InheritedWidget实现的,所以数据也是有流向的,所以我们需要把ChangeNotifierProvider.value放在两个界面上面的位置,这样我们一旦更新一个页面的数据另外一个页面就也可以获取到。

首先,我们定一个入口

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void main() {
  runApp(new MaterialApp(
    home:   MyApp(),
  ));
}

然后定义入口Widget

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider.value(
        value: UserInfoModel(),
        child: MaterialApp(
          home: FirstPage(),
        )
    );
  }
}

第一个界面我们定义一个按钮和一个Text用来显示第二个界面更新的数据

我们使用context.watch()方法来获取到对象,并监听

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text("ProviderTitle"),
      ),
      body: Center(
        child: Column(
          children: [
            Text( context.watch<UserInfoModel>().nickName),
            RaisedButton(
              child: Text("去设置界面"),
              onPressed: () {
                Navigator.push(context, MaterialPageRoute(builder: (context) {
                  return SecondPage();
                }));
              },
            )
          ],
        ),
      ),
    );
  }
}

第二个界面我们定义一个输入框和一个按钮,点击按钮就把输入框的值设置给Provider

我们使用 Provider.of(context)方法来获取监听对象并进行修改操作。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    TextEditingController _unameController = TextEditingController();
  var userModel=  Provider.of<UserInfoModel>(context);
    return Scaffold(
      appBar: AppBar(
        title: Text("ProviderTitle"),
      ),
      body: Column(
        children: [
          TextField(
            autofocus: true,
            controller: _unameController,
            decoration: InputDecoration(
                labelText: "用户名",
                hintText: "用户名或邮箱",
                prefixIcon: Icon(Icons.person)),
          ),
          RaisedButton(
            onPressed: () {
              userModel.updateNickName(_unameController.text);

            },
            child: Text("设置"),
          )
        ],
      ),
    );
  }
}
同时管理多个数据

在上面我们介绍了如何通过Provider来管理用户名数据,那么如果涉及多个数据我们该如何来管理呢?

通常情况下我们可以把多个数据封装成一个完整的数据来进行操作,这种方法在数据间相互关联性比较接近的情况下是可以实现的,但是如何遇到数据关系不大的情况下还采用这种方法的话就会造成界面Widget不必要的重绘。

当然,Provider也为我们提供了解决方法,MultiProvider可以让我们同时管理多个数据。

还是以上面的例子来进行说明,我们在前面用户名的基础上又增加了一个“家庭地址”,在第一个界面新增一个Text用来显示家庭地址,在第二个界面新增一个输入框用来输入家庭地址。

首先我们定义一个用来管理地址的Model

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class UserLocationModel with ChangeNotifier {
  String _address = "address";

  // 读方法
Stringget address => _address;

// 写方法
void setAddress(String address) {
    _address = address;
    notifyListeners(); // 通知听众刷新
  }
}

然后我们使用MultiProvider来管理多个Model

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 通过 Provider 组件封装数据资源
return MultiProvider(providers: [
      ChangeNotifierProvider.value(value: UserInfoModel()),
      ChangeNotifierProvider.value(value: UserLocationModel())
    ], child: MaterialApp(home: FirstPage()));
  }
}

然后在第一个界面接收并显示数据

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("ProviderTitle"),
      ),
      body: Center(
        child: Column(
          children: [
            Text("用户名:${context.watch<UserInfoModel>().nickName}"),
            Text("家庭地址:${context.watch<UserLocationModel>().address}"),
            RaisedButton(
              child: Text("去设置界面"),
              onPressed: () {
                Navigator.push(context, MaterialPageRoute(builder: (context) {
                  return SecondPage();
                }));
              },
            )
          ],
        ),
      ),
    );
  }
}

在第二个界面设置数据

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    TextEditingController _unameController = TextEditingController();
    TextEditingController _homeController = TextEditingController();
    var userModel = Provider.of<UserInfoModel>(context);
    var homeModel = Provider.of<UserLocationModel>(context);
    return Scaffold(
      appBar: AppBar(
        title: Text("ProviderTitle"),
      ),
      body: Column(
        children: [
          TextField(
            autofocus: true,
            controller: _unameController,
            decoration: InputDecoration(
                labelText: "用户名",
                hintText: "用户名或邮箱",
                prefixIcon: Icon(Icons.person)),
          ),

          RaisedButton(
            onPressed: () {
                userModel.updateNickName(_unameController.text);
            },
            child: Text("设置用户名"),
          ),
          TextField(
            autofocus: true,
            controller: _homeController,
            decoration: InputDecoration(
                labelText: "家庭地址",
                hintText: "请输入家庭地址",
                prefixIcon: Icon(Icons.person)),
          ),
          RaisedButton(
            onPressed: () {
            homeModel.setAddress(_homeController.text);

            },
            child: Text("设置家庭地址"),
          ),

        ],
      ),
    );
  }
}

当然我们也可以使用Consumer2方法来获取多个数据的传递,这样就不需要再创建UserInfoModel和UserLocationModel了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("ProviderTitle"),
      ),
      body: Center(
          child: Consumer2<UserInfoModel, UserLocationModel>(
//builder 函数以参数的形式提供了数据资源
              builder: (context, UserInfoModel userInfoModel,
                      UserLocationModel userLocationModel, _) =>
                  Column(
                    children: [
                      Text("用户名:${userInfoModel.nickName}"),
                      Text("家庭地址:${userLocationModel.address}"),
                      RaisedButton(
                        child: Text("去设置界面"),
                        onPressed: () {
                          Navigator.push(context,
                              MaterialPageRoute(builder: (context) {
                            return SecondPage();
                          }));
                        },
                      )
                    ],
                  ))),
    );
  }
}

小结

  • Provider是对InheritedWidget的封装方便我们在多个界面间传递数据
  • Provider支持同时管理多个数据的状态
  • 可以借助与Consumer-Consumer6方法来管理多个数据状态
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-06-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 flutter开发者 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Redis实战12-优惠券实现一人一单功能
在上一篇, Redis实战11-实现优惠券秒杀下单 我们已经把超卖问题解决了。接下来,我们来开发,优惠券一人一单功能。通过本文学习,您将有如下收获:
凯哥Java
2023/02/18
1K0
Redis实战12-优惠券实现一人一单功能
Redis解决秒杀下单
上述就是实现最基本的优惠卷下单功能。当然真实的业务场景绝对不会是向我们这么简单的。
用户11097514
2024/05/30
1920
Redis解决秒杀下单
公司新来一个同事,把优惠券系统设计的炉火纯青!
如:A优惠券一共发行120张,每一个用户可以领取140张,当一个用户领取优惠券成功的时候,把领取的记录写入到另外一个表中(这张表我们暂且称为表B)
Java团长
2022/12/20
1.6K1
公司新来一个同事,把优惠券系统设计的炉火纯青!
【 Redis | 实战篇 秒杀实现 】
实现全局ID生成器,秒杀优惠券(基于乐观锁解决超卖问题),秒杀的一人一单(单机与集群线程安全问题)
张哈大
2025/05/31
840
【 Redis | 实战篇 秒杀实现 】
【 Redis | 实战篇 秒杀优化 】
前述:由于我们发生了集群问题(不同的jvm下的监视器对象不同,那么同一把锁可以获取多次),【 Redis | 实战篇 秒杀实现 】-CSDN博客(问题描述),因此无法实现多个jvm线程的互斥
张哈大
2025/05/31
790
【 Redis | 实战篇 秒杀优化 】
微服务架构之:Redis分布式锁
在单体架构上,乐观锁和悲观锁可以锁住并发情况下的同步代码块,我们多使用synchronized来对方法加锁。但是在配上负载均衡的集群模式下, 普通的synchronized是无法锁住从两台服务器同时进入的请求 。
用户6256742
2024/07/15
1370
微服务架构之:Redis分布式锁
​【五一创作】基于mysql关系型实现分布式锁
在多线程高并发场景下,为了保证资源的线程安全问题,jdk为我们提供了synchronized关键字和 ReentrantLock可重入锁,但是它们只能保证一个jvm内的线程安全。在分布式集群、微服务、云原生横行的当下,如何保证不同进程、不同服务、不同机器的线程安全问题,jdk并没有给我们提供既有的解决方案。此时,我们就必须借助于相关技术手动实现了。目前主流的实现有三种方式: 1. 基于mysql关系型实现 2. 基于redis非关系型数据实现 3. 基于zookeeper实现
一个风轻云淡
2023/10/15
3790
​【五一创作】基于mysql关系型实现分布式锁
Redis分布式锁
我比较喜欢做全套的,一个Redis分布式锁的应用示例,我准备了Redis各种环境、SpringBoot部署两个服务、用tengine做这两个服务的负载均衡、用Jmeter做压力测试,可谓是麻雀虽小,五脏俱全。
行百里er
2020/12/02
9160
Redis分布式锁
多级缓存降低高并发压力
传统的缓存策略一般是请求到达Tomcat后,先查询Redis,如果未命中则查询数据库,如图:
不吃紫菜
2023/02/23
1.5K0
Nginx+Tomcat关于Session的管理【面试+工作】
解决办法安装epel:EPEL是企业版 Linux 附加软件包的简称,EPEL是一个由Fedora特别兴趣小组创建、维护并管理的,针对 红帽企业版 Linux(RHEL)及其衍生发行版(比如 CentOS、Scientific Linux、Oracle Enterprise Linux)的一个高质量附加软件包项目;
Java帮帮
2018/11/22
9710
Java锁好用还是分布式锁好用?
目前的项目单体结构的基本上已经没有了,大多是分布式集群或者是微服务这些。既然是多台服务器。就免不了资源的共享问题。既然是资源共享就免不了并发的问题。针对这些问题,redis也给出了一个很好的解决方案,那就是分布式锁。这篇文章主要是针对为什么需要使用分布式锁这个话题来展开讨论的。不喜勿喷,准备一套金三银四总结的面试真题共享给你们实战备用!
Java程序猿
2021/05/27
7430
Tomcat_02_应用部署
第一种方式是直接将程序目录放在webapps目录下面,这种方式大家已经明白了,就不多说了。
Cyylog
2020/08/19
7860
Nginx+Tomcat+Redis负载均衡Session共享实现超级简单(CentOS6.9系统 Java版本)
第一步Nginx+Tomcat 实现负载均衡的测试  相关软件环境 软件名称 版本号 版本说明 Java 1.7 linux版本 Tomcat 8081 7.x linux版本 Tomcat 8082 7.x linux版本 Redis 3.2.9 linux版本 Nginx 1.12.0 linux版本 CentOS 6.9 ---------- MySql 系统自带 ---------- 链接: https://pan.baidu.com/s/1i5U3srj 密
小帅丶
2018/02/09
1.4K0
Nginx+Tomcat+Redis负载均衡Session共享实现超级简单(CentOS6.9系统 Java版本)
Nginx实战应用-负载均衡
  本文我们继续来介绍nginx的实际操作,本文来介绍下Nginx的负载均衡的实现。
用户4919348
2020/05/25
6900
Nginx实战应用-负载均衡
那些年,我们见过的Java服务端“问题”
明代著名的心学集大成者王阳明先生在《传习录》中有云:“道无精粗,人之所见有精粗。如这一间房,人初进来,只见一个大规模如此。处久,便柱壁之类,一一看得明白。再久,如柱上有些文藻,细细都看出来。然只是一间房。”
macrozheng
2019/10/01
9430
Java面试:2021.05.28
大体来说,经历以下过程:接口需求调研、接口测试工具选择、接口测试用例编写、接口测试执行、接口测试回归、接口测试自动化持续集成。具体来说,接口测试流程分成以下九步:
夕梦
2021/06/03
3490
Java面试:2021.05.28
Redis常见面试题(二):redis分布式锁、redisson;Redis集群、主从复制,哨兵模式,分片集群;Redis为什么这么快,I/O多路复用模型
还记得Redis使用场景、缓存穿透、缓存击穿、缓存雪崩、Redis持久化、数据过期策略、数据淘汰策略吗?如果忘记可以到这里重新温习, Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略。
寻求出路的程序媛
2024/07/24
2.9K0
Redis常见面试题(二):redis分布式锁、redisson;Redis集群、主从复制,哨兵模式,分片集群;Redis为什么这么快,I/O多路复用模型
2022Java微服务最全面试题集
分布式架构就是将传统结构按照模块进行拆分,不同的人负责不同的模块,不会产生代码冲突问题,方便开发。
张哥编程
2024/12/13
1120
Redis分布式事务锁的应用——秒杀、超卖 简单例子 (下)
上一篇文章介绍了Redisson的分布式锁原理,这篇文章来验证一下Redisson分布式锁的作用。
HaC
2020/12/30
1.1K0
Redis分布式事务锁的应用——秒杀、超卖 简单例子 (下)
还能用mysql实现分布式锁?
之前的文章中通过电商场景中秒杀的例子和大家分享了单体架构中锁的使用方式,但是现在很多应用系统都是相当庞大的,很多应用系统都是微服务的架构体系,那么在这种跨jvm的场景下,我们又该如何去解决并发。
程序员老猫
2021/01/06
1.1K0
还能用mysql实现分布式锁?
推荐阅读
相关推荐
Redis实战12-优惠券实现一人一单功能
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验