Flutter笔记 状态模式、控制器模式、GetX控制器和服务
作者 :李俊才 (jcLee95):blog.csdn.net/qq_28550263
邮箱 : 291148484@163.com
本文地址 :blog.csdn.net/qq_28550263...
【简介】本文聊一聊状态提升、控制器模式,GetX简单状态管理与响应式状态管理、GetX服务的相关思想和使用。
目 录* * *
- 1. 概述
- 2. 状态提升模式
- 3. 控制器模式与状态管理
- 4. GetxController 与控制器模式
- 5. GetxService 与全局状态管理
- 6. GetxController 和 GetxService的比较
1. 概述
在Flutter中,状态管理是一个重要的主题,它涉及到如何存储和更新应用的数据以及如何在组件之间共享数据。状态管理的方法有很多种,包括状态提升、控制器模式、响应式编程等。每种方法都有其优点和适用场景。
状态提升是一种简单的状态管理方法,它通过将状态放在组件树的上层来实现状态的共享。但是,状态提升可能会导致组件树过于复杂,而且不适用于全局状态的管理。
控制器模式是一种更加灵活的状态管理方法,它通过将状态封装在控制器对象中,然后通过控制器来管理状态。控制器模式可以有效地管理全局状态,而且可以避免组件树过于复杂。
响应式编程是一种基于数据流的编程模式,它可以使状态的更新变得更加直观和易于理解。在响应式编程中,状态被视为数据流,组件可以监听数据流的变化并根据变化来更新自己。
GetX库提供了一种简单而强大的状态管理方法,它结合了控制器模式和响应式编程的优点。在GetX中,你可以创建一个继承自GetxController或GetxService的类来保存状态,然后在状态改变时调用update()方法或者使用.obs来通知所有监听这个状态的组件。
接下来,本文具体聊一聊状态提升、控制器模式,GetX简单状态管理与响应式状态管理、GetX服务的相关思想和使用。
2. 状态提升模式
2.1 状态提升的基本概念
状态提升(State Lifting) 是一种在 Flutter 中常用的状态管理模式,其基本思想是将状态放在需要这个状态的最小公共祖先组件上。这样,所有需要这个状态的子组件都可以通过祖先组件来访问和修改这个状态。例如,如果两个兄弟组件都需要访问和修改同一个状态,那么这个状态就应该放在它们的父组件上。
详细说来,这种模式通常用于处理以下情况:
- 共享数据: 当多个组件需要访问和共享相同的数据时,将状态提升到这些组件的共同祖先组件中,以便它们可以共享数据。
- 状态同步 : 当某个状态需要被多个组件 修改 时,将这个状态提升到共同的父组件,由父组件负责管理和更新状态,然后将状态传递给子组件。
2.2 状态提升的缺陷
虽然状态提升模式在一些简单的场景下工作得很好,但是它也有一些缺陷。
由于每个组件都可以有自己的 内部状态(即局部状态),但当多个组件之间需要共享状态或协同工作时,状态提升就变得非常有用。但是在很多实际开发场景中并不是说你想提升状态就可以提升状态。显而易见的是,如果我们封装一个第三方组件库,不可能在组件发布后去库的使用者的代码里提升状态,但是使用者又有可能需要用到这些状态来控制我们所封装的组件,因此这种情况下状态提升并不是可行的解决方案。
另外一个方面,状态提升在 Flutter 中将很容易导致扩大刷新范围,浪费性能。因此需要一种有效的解决方案来弥补状态提升的不足。这个解决方案就是所谓的 控制器模式。
3. 控制器模式与状态管理
3.1 ChangeNotifier 与 ListerableBuilder(改变通知机制)
顾名思义,ChangeNotifier (改变通知) 可以在状态改变时通知其监听器,是一个可以混入到类中的类。你可以创建一个继承自 ChangeNotifier 的类来保存状态,然后在状态改变时调用 notifyListeners
() 方法来通知所有监听这个状态的组件。
其中需要指出的是,ChangeNotifier 实现了 Listenable 接口,用于提供一个可以发送变化通知的对象:
- Listenable 接口定义了两个方法:
addListener()
和removeListener()
,这两个方法分别用于添加和移除监听器。任何实现了 Listenable 接口的对象都可以被其他对象监听,当 Listenable 对象的状态发生变化时,它可以通知所有的监听器。(实际上是 发布-订阅模式-见《发布订阅模式原理及其应用》,地址:jclee95.blog.csdn.net/article/det...) - ChangeNotifier 是 Listenable 的一个具体实现,它提供了一个
notifyListeners()
方法,可以在状态改变时调用,以通知所有的监听器。ChangeNotifier 内部维护了一个监听器列表,当你调用addListener()
方法时,监听器会被添加到这个列表中;当你调用removeListener()
方法时,监听器会从这个列表中移除。
例如,你可以创建一个 Counter 类,它继承自 ChangeNotifier ,并有一个 count
状态和一个 increment
方法:
scala
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
然后,你可以使用 ListenableBuilder 来 监听 这个 Counter 对象。ListenableBuilder 函数会在每次 ChangeNotifier 调用 notifyListeners() 以实现 Counter 对象的 状态改变 时被调用。
scala
class CounterWidget extends StatelessWidget {
final Counter counter;
CounterWidget({@required this.counter});
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: counter,
builder: (context, _) {
return Text('Count: ${counter.count}');
},
);
}
}
其中:
- 当你创建一个 ListenableBuilder 并传入一个 Listenable 对象(ChangeNotifier 是 Listenable 的实现)时,ListenableBuilder 会将自己添加到 Listenable 的监听器列表中。
- 当 Listenable 对象的状态改变并调用
notifyListeners()
方法时,所有的监听器(包括ListenableBuilder)都会收到通知。 - 当 ListenableBuilder 收到通知时,它会调用其builder函数来重建子组件。builder函数会接收到当前的 BuildContext 、Listenable 对象,以及一个可选的
child
参数,然后返回一个新的 Widget。 - ListenableBuilder 会将 builder 函数返回的 新 Widget 显示在屏幕上,从而更新UI的效果。
功能上 ListenableBuilder 和 AnimationBuilder 是一样的。
因此在封装组件时,经常使用控制器来命名这个基于 改变通知(发布订阅) 的类:
scala
/// 计数器控制器类
///
/// - 存储计数器状态;
/// - 提供改变状态的方法作为外部改变状态的接口。
class CounterController with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
3.2 控制器模式是如何工作的
控制器模式是一种更加灵活的状态管理模式。在这种模式下,状态被保存在一个或多个控制器对象中,而不是直接保存在组件中。组件可以创建和管理这些控制器对象,也可以通过它们来访问和修改状态。
当状态改变时,控制器会通知所有监听这个状态的组件,这样这些组件就可以根据新的状态来更新自己。因为状态被保存在控制器中,所以它可以被任何可以访问到这个控制器的组件共享,这使得状态管理变得更加灵活和高效。
状态提升时,我们仅仅时把状态放在了层级更高的组件,但是控制器模式将状态放在一个独立的类中,这个类不仅用于存储状态,也提供相应的改变方法。实际上,上一节混入了 ChangeNotifier 的 Counter 类就是一个控制器。
为什么控制器要混入或继承于ChangeNotifier?
因为我们的目标是状态改变后能够及时的更新UI。
在 Flutter 中,提供 ChangeNotifier - ListenableBuilder 机制:
前者用于控制器类------因为控制器类是状态改变的源:
- 所有改变状态变量的操作被封装在控制器类中,以接口的形式暴露给外部使用;
- 在修改的接口方法中,每当数据更改数据后调用
notifyListeners()
方法完成通知监听器。
后者用于 build 方法的某个局部需要依赖于数据更新的UI中:
- ListenableBuilder 的 builder 方法在监听器被通知后使用新的数据进行重构;
4. GetxController 与控制器模式
4.1 简单状态管理
在 GetX 库中,你可以创建一个继承自 GetxController 的类来保存状态,然后在状态改变时调用 update() 方法来通知所有监听这个状态的组件。
scala
class CounterController extends GetxController {
int count = 0;
void increment() {
count++;
update();
}
}
然后,你可以在UI类中使用 GetBuilder 来监听这个 CounterController 对象。当 CounterController 对象的状态改变时,GetBuilder 会自动重建,从而更新UI。
less
class CounterWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counterController = Get.put(CounterController());
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: Center(
child: GetBuilder<CounterController>(
builder: (controller) => Text('Count: ${controller.count}'),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: counterController.increment,
),
);
}
}
仅仅从使用的角度上看:
- 在控制器类中:
- 从继承于 ChangeNotifier 变成了继承于 GetxController;
- 从使用
notifyListeners();
函数通知更新UI变成了 使用update();
函数更新UI。
- 在 UI 的build函数的需要更新处:
- 从使用 ListenableBuilder 类包括需要依赖数据更新部分改成了使用 GetBuilder 类。
4.2 响应式状态管理
GetX的响应式状态管理提供了一种用起来更加方便地方式------不再需要使用 update()
方法。但是这意味着需要在每一个变量上做些手脚------添加.obs
变成"响应式变量"。
在GetX库内部,.obs
是一个扩展方法,它可以用于将普通的Dart值(如String、int、double等)转换为可观察的 Rx 对象。------这是因为在 Dart 语言中,你可以通过 扩展(extension) 语法来为已有的类型添加新的方法或属性。GetX库就使用了这个特性,为Dart的基本类型添加了 .obs
扩展方法。
所以,当你在一个String、int、double等值后面调用.obs时,你实际上是在创建一个新的Rx对象,这个对象的初始值就是这个值。例如,var count = 0.obs;就等价于var count = Rx(0);。
Rx对象是可观察的,你可以使用value属性来获取或设置它的值,也可以使用addListener()方法来添加监听器。当Rx对象的值改变时,所有的监听器都会收到通知。这就是GetX的响应式状态管理的基础。
基于响应式转台管理,控制器类调整为:
scala
// 控制器类
class CounterController extends GetxController {
RxInt count = 0.obs;
void increment() {
count++;
}
}
而在UI部分,也不再使用 ChangeNotifier 的ListenableBuilder 或者简单状态管理的GetBuilder ,而是由 Obx进行包裹,例如:
less
// 界面组件
class CounterView extends StatelessWidget {
final CounterController controller = Get.put(CounterController());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Counter App with GetX'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Obx(() => Text('Count: ${controller.count}')),
ElevatedButton(
onPressed: () => controller.increment(),
child: Text('Increment'),
),
],
),
),
);
}
}
5. GetxService 与全局状态管理
实际上 GetxService 和 的功能是很像的,我们可以将控制器类改为一个服务类,比如计数器的例子:
scala
class CounterService extends GetxService {
var count = 0.obs;
void increment() {
count.value++;
}
}
然后,你可以在UI类中使用Obx来监听这个CounterService对象。当CounterService对象的状态改变时,Obx会自动重建,从而更新UI:
less
class CounterWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counterService = Get.put(CounterService());
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: Center(
child: Obx(() => Text('Count: ${counterService.count.value}')),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: counterService.increment,
),
);
}
}
从使用上看没有什么不同。但是GetxService是一个长生命周期的类,一旦被创建,就会一直存在,直到应用被关闭或者你手动调用Get.reset()。
6. GetxController 和 GetxService的比较
GetxController 和 GetxService 都是 GetX 库中的核心组件,它们都有生命周期方法(onInit
(), onReady
(), onClose
()),并且都可以用于 管理状态和依赖。但是,它们的主要区别在于它们的存活时间和用途不一样。
6.1 GetxController 的应用场景
GetxController 是一个用于状态管理的类,它的实例可以通过 Get.put
() , Get.lazyPut
() , Get.putAsync
() , Get.create
() 等方法创建并绑定到一个生命周期。当与绑定的页面不再需要时, GetX会自动删除GetxController的实例 以释放内存。因此,GetxController通常用于页面和小部件的 局部状态管理,例如用户界面的交互、表单状态、主题颜色等。
6.2 GetxService 的应用场景
GetxService 是一个长期存活在应用中的类,它的实例一旦被创建,就不会被自动删除,除非你手动调用Get.reset
() 。因此,GetxService 通常用于需要全局访问和长期存在的服务,例如用户认证、数据库操作、网络请求等。
最常见的就是认证和权限,者往往是整个应用生命周期都需要的,因此我们经常定义各异认证服务,用于处理应用中与认证相关的状态:
scala
class AuthService extends GetxService {
Future<AuthService> init() async {
// Initialize your class
}
}
在GetX中,Get.reset()方法用于清除所有的依赖项,包括GetxController和GetxService的实例。这个方法通常在你需要完全重置应用状态时使用,例如用户注销登录时。
然而,如果你有一个场景需要重置CounterService的状态,例如用户注销登录时,你可以调用Get.reset()。例如:
csharp
void userLogout() {
// ...其他的注销逻辑...
// 重置应用状态
Get.reset();
}
需要指出的是,Get.reset()会清除所有的依赖项,包括所有的GetxController和GetxService的实例。如果你只想重置CounterService的状态,你可以在CounterService中添加一个重置状态的方法,然后在需要的地方调用这个方法。比如:
scss
void userLogout() {
// ...其他的注销逻辑...
// 重置CounterService的状态
Get.find<CounterService>().reset();
}