Flutter 撸一个简单的状态管理
序言
好久没写Flutter了,转眼都3.7了,这段时间捡起来发现竟然没忘记语法(也许是前一段时间写Compose的原因,它俩可太像了)
看过 GetX 源码的都应该知道,GetX中的状态管理的核心其实就是对StatefulWidget的最小封装;也就是说 GetX 就是 setState((){})。
本文参照它的 最小StatefulWidget 来撸一个简单的状态管理。
本文不会深剖
GetX的源码;文章内容不复杂,也不难理解,代码也没考虑到多层次关系,更多的还是偏小白向,学的还是思想嘛[dog]。
那么!开撸!
封装最小可变的 StatefulWidget
老生常谈的东西了,与它同辈的还有一个StatelessWidget无状态的,这两个也是Flutter中对于Widget最基本的实现。
还是计数器的例子,官方 New Flutter Project 生成的模板代码就般了(不然就有水文的嫌疑了)。
按照Flutter中一般的状态管理框架,一定会有一个Controller作为视图控制器,将视图管理与数据进行关联(类似于MVP的设计思想),接口代码:
/// state_interfaces.dart
/// 顶层接口
/// Controller
abstract class IStateController {
// setState()
void refresh();
}
/// View
abstract class IStateView {
// setState()
void refresh();
}
这里为 IStateController 写一个抽象类StateController。
因为dart中没有
interfac关键字的实现,接口的定义也采用abstract,区分接口和抽象类的区别在于implements和extends两个关键字。
/// state_controller.dart
import 'state_interfaces.dart';
abstract class StateController implements IStateController {
@override
void refresh() {
//刷新视图的逻辑, 下文补齐
}
}
然后就是IStateView的实现类StateView,需要注意的是StateView作为可变组件的最小主体,它还应该继承至StatefulWidget。
又由于StateView作为最小的可变组件,因此这里将它定义为一个容器,这个容器内的所有Widget就是可变的,可以仔细品味下面的代码:
/// state_view.dart
import 'package:flutter/material.dart';
import 'package:test_state_view/state/state_controller.dart';
import 'state_interfaces.dart';
typedef StateViewBuilder<C extends StateController> = Widget Function(BuildContext context, C controller);
class StateView<C extends StateController> extends StatefulWidget {
const StateView({
Key? key,
required this.controller,
required this.builder,
}) : super(key: key);
final C controller;
final StateViewBuilder<C> builder;
@override
State<StateView> createState() => _StateViewState<C>();
}
class _StateViewState<C extends StateController> extends State<StateView<C>> implements IStateView {
@override
Widget build(BuildContext context) {
return widget.builder(
context,
widget.controller,
);
}
@override
void refresh() => setState(() {});
}
如此一来,Widget(这里命名为View) 便持有了 Controller 了;
接来下就是对Controller的设计,让他能够顺利的操作到State,达到setState((){})的效果;
这里需要做一个 多Widget 的考虑,可不能将 Controller 与某一个State 做死绑定;
为什么这么说呢?因为一个应用中是存在多个路由页面的,当需要在第二个页面中刷新第一个页面中的内容,如文章开头的图示,一旦Controller与State绑死,那第二个路由页对于第一个路由页Controller的获取就要复杂很多了,外加上Flutter中不允许反射,甚至可能无法获取。
因此,Controller 与 State 之间的关系为:一对多关系,这里就需要第三方介入来存储它们之间的关系了;
我们定义一个名为 StateManager 的管理类,让它来持有 Controller 和 State。
/// state_manager.dart
import 'state_interfaces.dart';
class StateManager {
const StateManager._();
static final Map<IStateController, List<IStateView>> _states = {};
static void putState(IStateController controller, IStateView state) {
//如果该Controller未被记录
if (!_states.containsKey(controller)) {
_states[controller] = [state];
return;
}
//如果该State不存在
if (!_states[controller]!.contains(state)) {
_states[controller]!.add(state);
}
}
static void removeState(IStateController controller, IStateView state) {
//如果该Controller未被记录
if (!_states.containsKey(controller)) return;
//如果该State不存在
if (_states[controller] == null || !_states[controller]!.contains(state)) return;
_states[controller]!.remove(state);
}
static void releaseState(IStateController controller) {
//如果该Controller未被记录
if (!_states.containsKey(controller)) return;
_states[controller]?.clear();
_states.remove(controller);
}
static void clear() {
_states.clear();
}
static List<IStateView> getStates(IStateController controller) {
return _states[controller] ?? [];
}
}
如此一来,就只需要将 Contoller 和 State 交由它管理,就可以实现一对多的效果;
我们修改一下 _StateViewState 的代码,分别增加 initState() 和 dispose() 方法,将 Controller 与 State 关联起来。
class _StateViewState<C extends StateController> extends State<StateView<C>> implements IStateView {
@override
void initState() {
super.initState();
StateManager.putState(widget.controller, this);
}
@override
Widget build(BuildContext context) {
return widget.builder(
context,
widget.controller,
);
}
@override
void dispose() {
super.dispose();
StateManager.removeState(widget.controller, this);
}
@override
void refresh() => setState(() {});
}
然后,就是 Controller 中的 refresh() 方法了,我们来补齐上文的刷新功能
/// state_controller.dart
import 'state_interfaces.dart';
import 'state_manager.dart';
abstract class StateController implements IStateController {
@override
void refresh() {
//刷新视图的逻辑
StateManager.getStates(this).forEach((state) {
state.refresh();
});
}
}
至此,最小 StatefulWidget 的封装结束。
这里的逻辑有些粗糙,只允许对应的
Controller有一个实例对象,否则这一套封装将没有意义了;当然了,出于程序方面考虑,应用中某个的
Controller也应该是单例的。
测试计数器
逻辑不复杂,代码很简单,测试Demo以及实现代码GitHub待会放评论区。