Flutter Provider 状态管理-基本认识、示例
“状态(State)管理” 在响应式编程中都是非常重要的存在,无论是 Flutter,还是 Vue、React(Web开发框架),在思想上其实都是一致的。 我最近一段时间都在啃 Google 的 Flutter,学习路径到状态管理的地方,正好 目前在社区内 Provider、BLoC、GetX、MobX、Redux 等,都是受欢迎的状态管理包。 本文基础建立在空安全支持、Flutter 2.8、Dart 2 的版本下使用。 [√] Flutter (Channel stable, 2.8.0, on Microsoft Windows [Version 10.0.22000.318], locale zh-CN) 这一直是一个很长久的话题,已经了解的话可以跳过这个部分。 就像下面这张图一样,在项目初期或者是简单构建的应用,也许直接使用数据进行视图渲染就可行了。 模拟简单结构 但是随着长时间的迭代或是构建庞大的项目,现实情况会存在多个组件共享一个状态或是组件之间多层的嵌套等等,过多的数据状态和业务逻辑会使整体结构变得零散且难以管理,也会使得代码的可读性和可维护性大大降低。 模拟复杂结构 所以我们在需要的时候可以将业务逻辑和数据进行抽离,以方便我们更好地进行管理。 ——引用自Provider官方 详细的 接下来会对 计数器简单示例 counter_view_model.dart 将具体业务逻辑和数据状态抽离到视图模型中,mixin 混入 main.dart 这是我们最顶层的入口,使用 这里的 如果需要挂载多个模型,我们还可以利用 counter_view.dart 这是我们计数器的首页,状态数据需要渲染的页面使用 “消费者” 之一的 context: 当前的上下文 除此之外 “消费者” 还有 counter_operation_view.dart 这是第二个页面用于触发业务逻辑,主要简单模拟跨组件触发业务逻辑改变数据状态,利用最简单的 “消费者” 我们也可以在使用中利用 本文主要介绍了 [1]Flutter完整开发实战详解(十五、全面理解State与Provider) 转载请遵循 协议许可水 写一篇文章巩固一下(算是重复造轮子,因为市面上相关教学文章已经非常丰富了,我在这里主要是写上自己的理解和示例,用以巩固)。
我经过实际使用对比筛选多个库,最终选择了较好上手并且入选 Flutter Favorite 受到官方推荐的 Provider
进行使用(并非官方出品)。环境
以下是我当前使用的环境:
[√] Dart SDK version: 2.15.1 (stable) on “windows_x64”
[√] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
[√] Android Studio (version 2020.3)
[√] VS Code (version 1.63.1)Packages
dependencies:
provider: ^6.0.1
为什么要使用状态管理
Provider优势
Provider
对 InheritedWidget
组件的上层封装,使其更易用,更易复用。
使用 Provider
而非手动书写 InheritedWidget
,有以下的优势:DevTools
InheritedWidget
的方式(参考 Provider.of / Consumer / Selector )Provider
文档:链接名称 描述 Provider 最基础的 provider 组成,接收一个任意值并暴露它。 ListenableProvider 供可监听对象使用的特殊 provider。ListenableProvider 会监听对象,并在监听器被调用时更新依赖此对象的 widgets。 ChangeNotifierProvider 为 ChangeNotifier 提供的 ListenableProvider 规范,会在需要时自动调用 ChangeNotifier.dispose。 ValueListenableProvider 监听 ValueListenable,并且只暴露出 ValueListenable.value。 StreamProvider 监听流,并暴露出当前的最新值。 FutureProvider 接收一个 Future,并在其进入 complete 状态时更新依赖它的组件。 ······ ······ 基础示例
Provider
的基础用法进行实例使用,实现在两个页面的计数器效果,让我们有个简单的认识。
为了思维能够连贯清晰,我将所有代码都贴了出来(可以直接拷贝到项目中测试),文章篇幅会因此变长,阅读过程只需要关注代码中和 Provider
相关的部分。示例基本结构
├── lib
│ ├── view_models # 视图模型
│ │ └── counter_view_model.dart # Counter视图模型
│ ├── views # 视图
│ │ ├── counter_operation_view.dart # CounterOperation视图
│ │ └── counter_view.dart # Counter视图
│ └── main.dart # 主入口
一、创建ViewModel视图模型
import 'package:flutter/material.dart';
class CounterViewModel with ChangeNotifier {
int _count = 0;
void increment() {
_count++;
notifyListeners();
}
void decrement() {
if (_count > 0) {
_count--;
}
notifyListeners();
}
int get count => _count;
}
ChangeNotifier
类,帮助我们监听模型中的状态变化,根据需要在方法中调用 notifyListeners()
主动通知渲染视图。
因 Flutter 只有当树中的数据与传入的新数据不同时才会更新,所以在数据不改变的情况下多次调用 notifyListeners()
,也只会渲染一次视图。
将私有变量 _count
通过 get
暴露出去,并且提供了两个计算方法 increment()
、decrement()
。二、挂载根节点状态共享
import 'package:flutter/material.dart';
/// packages
import 'package:provider/provider.dart';
/// view_models
import 'package:flutter_demo_1/view_models/counter_view_model.dart';
/// views
import 'package:flutter_demo_1/views/counter_view.dart';
import 'package:flutter_demo_1/views/counter_operation_view.dart';
void main() {
runApp(const CounterApp());
}
class CounterApp extends StatelessWidget {
const CounterApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
/// Provider
return ChangeNotifierProvider(
create: (_) => CounterViewModel(),
child: MaterialApp(
title: 'Provider',
initialRoute: "/counter",
routes: {
'/counter': (context) => const CounterView(),
'/counter_operation': (context) => const CounterOperationView(),
},
),
);
}
}
ChangeNotifierProvider
将我们的 CounterViewModel
挂载到根节点为子节点进行状态共享(这里用作演示挂载到最顶层入口全局处,现实情况千万不能因为偷懒所有都挂载到最顶层根节点,一定要根据需求判断是进行局部挂载还是全局挂载,否则会影响应用的性能)。ChangeNotifierProvider
作为 “提供者” 之一,他会监听模型状态的变化,当数据变化时,他会通知 “消费者” 进行重建,在需要时会自动清理资源。MultiProvider
使我们的结构更方便阅读 ,比如下面这样:return MultiProvider(
providers: [
ChangeNotifierProvider<Model1>(create: (_) => Model1()),
ChangeNotifierProvider<Model2>(create: (_) => Model2()),
/// 更多...
],
child: MaterialApp(
home: Example(),
),
);
三、Counter页渲染数据
import 'package:flutter/material.dart';
/// packages
import 'package:provider/provider.dart';
/// view_models
import 'package:flutter_demo_1/view_models/counter_view_model.dart';
class CounterView extends StatelessWidget {
const CounterView({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.black,
child: const Icon(Icons.arrow_upward),
onPressed: () {
Navigator.pushNamed(context, '/counter_operation');
},
),
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
body: const SafeArea(
child: Counter(),
),
);
}
}
class Counter extends StatelessWidget {
const Counter({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Center(
child: SizedBox(
height: 128.0,
width: 128.0,
child: DecoratedBox(
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [
Color(0xFFE3B2CB),
Color(0xFFE3B2CB),
],
),
borderRadius: BorderRadius.circular(36.0),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
"Count",
style: TextStyle(
fontSize: 16.0,
),
),
/// Provider
Consumer<CounterViewModel>(
builder: (_, CounterViewModel counterViewModel, child) {
int _count = counterViewModel.count;
return Text(
_count.toString(),
style: const TextStyle(
fontSize: 48.0,
fontWeight: FontWeight.bold,
),
);
},
),
],
),
),
),
);
}
}
Consumer
,合理使用他能够将渲染粒度降低到只需要视图刷新的部分,复杂场景能有效提高应用性能,在实际使用场景非常常用,Consumer2-6
最多能同时装6个状态模型。Consumer
里面有三个属性:
T: 需要的根节点模型对象
child: 子组件(不需要刷新的部分)Provider.of(context)
、Selector
、InheritedContext系列
,在实际使用中都需要按照他们的特性结合进行使用,以达到最好疗效。四、CounterOperation页触发方法
import 'package:flutter/material.dart';
/// packages
import 'package:provider/provider.dart';
/// view_models
import 'package:flutter_demo_1/view_models/counter_view_model.dart';
class CounterOperationView extends StatelessWidget {
const CounterOperationView({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.black,
child: const Icon(Icons.arrow_downward),
onPressed: () {
Navigator.of(context).pop();
},
),
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
body: const SafeArea(
child: CounterOperation(),
),
);
}
}
class CounterOperation extends StatelessWidget {
const CounterOperation({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CounterButton(
icon: const Icon(Icons.add),
onPressed: () {
/// Provider
Provider.of<CounterViewModel>(context, listen: false).increment();
},
),
CounterButton(
icon: const Icon(Icons.remove),
onPressed: () {
/// Provider
Provider.of<CounterViewModel>(context, listen: false).decrement();
},
),
],
),
);
}
}
class CounterButton extends StatelessWidget {
const CounterButton({Key? key, required this.icon, required this.onPressed})
: super(key: key);
final Widget icon;
final Function() onPressed;
Widget build(BuildContext context) {
return Container(
height: 64.0,
width: 64.0,
margin: const EdgeInsets.all(8.0),
child: DecoratedBox(
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [
Color(0xFFE3B2CB),
Color(0xFFE3B2CB),
],
),
borderRadius: BorderRadius.circular(24.0),
),
child: Material(
color: Colors.transparent,
child: IconButton(
splashColor: Colors.black26,
icon: icon,
onPressed: onPressed,
),
),
),
);
}
}
Provider.of(context)
触发我们的业务方法,比如触发 CounterViewModel
中的增加方法 increment()
,这里对模型内部中私有的 _count
进行累加,并通过 notifyListeners()
主动通知渲染视图,也就达到了我们在其他组件或页面也能够更方便的进行共享调用的目的。Provider
给我们提供的语法糖 InheritedContext系列
替换掉 Provider.of(context)
进行使用。
比如 InheritedContext系列
其中之一的 BuildContext.watch
,让我们可以不再使用 Consumer
,就像下面这样:Widget build(BuildContext context) {
/// BuildContext.watch
final counterViewModel = context.watch<CounterViewModel>();
return ElevatedButton(
onPressed: () => counterViewModel.increment(),
child: Text("+1"),
);
);
总结
Provider
的基础认识和部分 Provider
的常见用法,相对于原生提供的 InheritedWidget
显然 Provider
更便于使用。
示例演示将计数器业务逻辑和数据状态抽离至 视图模型
,使用了 ChangeNotifierProvider
对入口根节点挂载了单个视图模型,利用 Consumer
监听并局部渲染显示数据,简单使用 Provider.of(context)
触发业务方法并通知改变渲染,在实际使用中也可以利用 MVVM
的设计思想为项目提供更好的状态管理。相关
[2]Flutter | 状态管理指南篇——Provider
本文所有内容严禁任何形式的盗用
本文作者:Amos
本文链接:https://blog.amooos.com/2021122001/