为什么要状态管理?
这是因为 Flutter 基于 声明式 构建 UI ,使用状态管理的目的之一就是解决「声明式」开发带来的问题。
「声明式」开发带来的问题?
- 逻辑和页面 UI 耦合,导致无法复用/单元测试、修改混乱等
- 难以跨组件 (跨页面) 访问数据
- 无法轻松的控制刷新范围 (页面 setState 的变化会导致全局页面的变化)
状态管理如何解决?
解决逻辑和页面UI耦合问题
View 中的逻辑代码抽离到 Presenter 层, View 只负责视图的构建。
解决难以跨组件 (跨页面) 访问数据的问题
Provider
- 依赖树机制,必须基于 context
- 提供了子组件访问上层的能力
Get
- 全局单例,任意位置可以存取
- 存在类型重复,内存回收问题
解决高层级 setState 引起不必要刷新的问题
Flutter 通过采用观察者模式解决,其关键在于两步:
- 观察者去订阅被观察的对象;
- 被观察的对象通知观察者。
Provider
提供了 ChangeNotifierProvider
Get
只需要提前调用 Get.put
方法存储 Counter
对象,为 GetBuilder
组件指定 Counter
作为泛型。因为 Get 基于单例,所以 GetBuilder
可以直接通过泛型获取到存入的对象,并在 builder 方法中暴露。这样 Counter
便与组件建立了监听关系,之后 Counter
的变动,只会驱动以它作为泛型的 GetBuilder
组件更新。
Flutter之状态管理(一) Provider_YUFENGSHI.LJ的博客-优快云博客
GetX
-
状态管理
简单状态管理器GetBuilder
class GetBuilder<T extends GetxController> extends StatefulWidget {
final GetControllerBuilder<T> builder;
final bool global;
final Object? id;
final String? tag;
final bool autoRemove;
final bool assignId;
final Object Function(T value)? filter;
final void Function(GetBuilderState<T> state)? initState,
dispose,
didChangeDependencies;
final void Function(GetBuilder oldWidget, GetBuilderState<T> state)?
didUpdateWidget;
final T? init;
.............}
- GetBuilder<T>,T是传入的业务类
- builder是一个可选参数里的必选参数,要求传入一个回调函数,这个回调函数传入的参数就是GetBuilder泛型类的实例
在GetController中,当变量发生改变时,使用update()来更新变量值。
在你的Stateless/Stateful类中,当调用increment时,使用GetBuilder来更新Text。
class pageTwoController extends GetxController {
List<String> content = ["111", "222", "333"];
String text = "pageTwo的controller";
var data = "";
void change() {
data = content[Random().nextInt(content.length)];
update();
}
}
GetBuilder<pageTwoController>(builder: (logic) {
return Text("${controler.data}");
}),
响应式状态管理器GetX
声明一个响应式变量
1 - 第一种是使用 Rx{Type}
。
// 建议使用初始值,但不是强制性的
final name = RxString('');
final isLogged = RxBool(false);
final count = RxInt(0);
final balance = RxDouble(0.0);
final items = RxList<String>([]);
final myMap = RxMap<String, int>({});
2 - 第二种是使用 Rx
,规定泛型 Rx<Type>
。
final name = Rx<String>('');
final isLogged = Rx<Bool>(false);
final count = Rx<Int>(0);
final balance = Rx<Double>(0.0);
final number = Rx<Num>(0)
final items = Rx<List<String>>([]);
final myMap = Rx<Map<String, int>>({});
// 自定义类 - 可以是任何类
final user = Rx<User>();
3 - 第三种更实用、更简单、更可取的方法,只需添加 .obs
作为value
的属性。
final name = ''.obs;
final isLogged = false.obs;
final count = 0.obs;
final balance = 0.0.obs;
final number = 0.obs;
final items = <String>[].obs;
final myMap = <String, int>{}.obs;
// 自定义类 - 可以是任何类
final user = User().obs;
更新值
而在UI中,当你想显示该值并在值变化时更新页面,只需在使用了观察对象的widget外面包裹一个Obx()
Obx(() => Text("${controller.name}"));
class pageTwoController extends GetxController {
RxInt i = 0.obs;
String text = "pageTwo的controller";
var data = "";
@override
void onInit() {
data = Get.arguments;
update();
}
void caculate(isadd) {
if (isadd) {
i++;
}
else {
i--;
}
}
}
Obx(() {
return Text("${controler.i}");}),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
controler.caculate(true);
},
child: Icon(Icons.add)),
SizedBox(width: 20,),
ElevatedButton(
onPressed: () {
controler.caculate(false);
},
child: Icon(Icons.remove)),
],
-
路由管理
普通路由导航
导航到新的页面。
Get.to(NextScreen());
关闭SnackBars、Dialogs、BottomSheets或任何你通常会用Navigator.pop(context)关闭的东西。
Get.back();
进入下一个页面,但没有返回上一个页面的选项(用于SplashScreens,登录页面等)。
Get.off(NextScreen());
进入下一个界面并取消之前的所有路由(在购物车、投票和测试中很有用)。
Get.offAll(NextScreen());
要导航到下一条路由,并在返回后立即接收或更新数据。
var data = await Get.to(Payment());
在另一个页面上,发送前一个路由的数据。
Get.back(result: 'success');
并使用它,例:
if(data == 'success') madeAnything();
别名路由导航
导航到下一个页面
Get.toNamed("/NextScreen");
浏览并删除前一个页面。
Get.offNamed("/NextScreen");
浏览并删除所有以前的页面。
Get.offAllNamed("/NextScreen");
要定义路由,使用GetMaterialApp。
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.zoom
),
],
)
);
}
要处理到未定义路线的导航(404错误),可以在GetMaterialApp中定义unknownRoute页面。
void main() {
runApp(
GetMaterialApp(
unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
],
)
);
}
发送数据到别名路由
只要发送你想要的参数即可。Get在这里接受任何东西,无论是一个字符串,一个Map,一个List,甚至一个类的实例。
Get.toNamed("/NextScreen", arguments: 'Get is the best');
在你的类或控制器上:
print(Get.arguments);
//print out: Get is the best
示例:
class pageOne extends StatelessWidget {
var controler=Get.find<pageOneController>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Page_one"),),
body: Container(
color: Colors.red,
child: Column(
children: [
Text("${controler.text}"),
ElevatedButton(
onPressed: (){
Get.toNamed('/page_two',arguments: "这是从pageone传过来的数据");
},
child: Text("page_one"),
),
],
),
),
);
}
}
class pageTwoController extends GetxController {
String text="pageTwo的controller";
var data="";
@override
void onInit() {
data=Get.arguments;
update();
}
}
动态网页链接
Get提供高级动态URL,就像在Web上一样。Web开发者可能已经在Flutter上想要这个功能了,Get也解决了这个问题。
Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo");
在你的controller/bloc/stateful/stateless类上:
print(Get.parameters['id']);
// out: 354
print(Get.parameters['name']);
// out: Enzo
你也可以用Get轻松接收NamedParameters。
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(
name: '/',
page: () => MyHomePage(),
),
GetPage(
name: '/profile/',
page: () => MyProfile(),
),
//你可以为有参数的路由定义一个不同的页面,也可以为没有参数的路由定义一个不同的页面,但是你必须在不接收参数的路由上使用斜杠"/",就像上面说的那样。
GetPage(
name: '/profile/:user',
page: () => UserProfile(),
),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.cupertino
),
],
)
);
}
发送别名路由数据
Get.toNamed("/second/34954");
在第二个页面上,通过参数获取数据
print(Get.parameters['user']);
// out: 34954
或像这样发送多个参数
Get.toNamed("/profile/34954?flag=true");
在第二个屏幕上,通常按参数获取数据
print(Get.parameters['user']);
print(Get.parameters['flag']);
// out: 34954 true
-
依赖管理
插入依赖的方式
Get.put()
Get.put<S>(
// 必备:你想得到保存的类,比如控制器或其他东西。
// 注:"S "意味着它可以是任何类型的类。
S dependency
// 可选:当你想要多个相同类型的类时,可以用这个方法。
// 因为你通常使用Get.find<Controller>()来获取一个类。
// 你需要使用标签来告诉你需要哪个实例。
// 必须是唯一的字符串
String tag,
// 可选:默认情况下,get会在实例不再使用后进行销毁
// (例如:一个已经销毁的视图的Controller)
// 但你可能需要这个实例在整个应用生命周期中保留在那里,就像一个sharedPreferences的实例或其他东西。
//所以你设置这个选项
// 默认值为false
bool permanent = false,
// 可选:允许你在测试中使用一个抽象类后,用另一个抽象类代替它,然后再进行测试。
// 默认为false
bool overrideAbstract = false,
// 可选:允许你使用函数而不是依赖(dependency)本身来创建依赖。
// 这个不常用
InstanceBuilderCallback<S> builder,
)
Get.lazyPut()
可以懒加载一个依赖,这样它只有在使用时才会被实例化。
Get.lazyPut<S>(
// 强制性:当你的类第一次被调用时,将被执行的方法。
InstanceBuilderCallback builder,
// 可选:和Get.put()一样,当你想让同一个类有多个不同的实例时,就会用到它。
// 必须是唯一的
String tag,
// 可选:类似于 "永久",
// 不同的是,当不使用时,实例会被丢弃,但当再次需要使用时,Get会重新创建实例,
// 就像 bindings api 中的 "SmartManagement.keepFactory "一样。
// 默认值为false
bool fenix = false
)
Get.putAsync()
如果你想注册一个异步实例,你可以使用Get.putAsync
。
Get.putAsync<SharedPreferences>(() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('counter', 12345);
return prefs;
});
Get.putAsync<YourAsyncClass>( () async => await YourAsyncClass() )
这都是你在使用putAsync时可以设置的选项。
Get.putAsync<S>(
// 必备:一个将被执行的异步方法,用于实例化你的类。
AsyncInstanceBuilderCallback<S> builder,
// 可选:和Get.put()一样,当你想让同一个类有多个不同的实例时,就会用到它。
// 必须是唯一的
String tag,
// 可选:与Get.put()相同,当你需要在整个应用程序中保持该实例的生命时使用。
// 默认值为false
bool permanent = false
)
使用依赖的方法
final controller = Get.find<Controller>();
// 或者
Controller controller = Get.find();
// 是的,它看起来像魔术,Get会找到你的控制器,并将其提供给你。
// 你可以实例化100万个控制器,Get总会给你正确的控制器。
Bindings
Binding类是一个将解耦依赖注入的类,同时 "Bindings "路由到状态管理器和依赖管理器。 这使得Get可以知道当使用某个控制器时,哪个页面正在显示,并知道在哪里以及如何销毁它。
-
创建一个类并实现Binding
class HomeBinding implements Bindings {}
你的IDE会自动要求你重写 "dependencies"方法,然后插入你要在该路由上使用的所有类。
class HomeBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<HomeController>(() => HomeController());
Get.put<Service>(()=> Api());
}
}
class DetailsBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<DetailsController>(() => DetailsController());
}
}
- 使用别名路由:
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: HomeBinding(),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: DetailsBinding(),
),
];
- 使用正常路由
Get.to(Home(), binding: HomeBinding());
Get.to(DetailsView(), binding: DetailsBinding())
Binding类在调用路由时被调用,你可以在你的GetMaterialApp中创建一个 "initialBinding "来插入所有将要创建的依赖关系。
GetMaterialApp(
initialBinding: SampleBind(),
home: Home(),
);
class GetXpage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: "Flutter Demo",
debugShowCheckedModeBanner: false,
initialBinding: bind1(),
getPages: getRouters.getpages,
home: HYHomePage(),
);
}
}
class HYHomePage extends StatelessWidget {
const HYHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("GetX"),
),
body: Center(
child:Container(
child:ElevatedButton(
onPressed: (){
Get.toNamed('/page_one');
},
child: Text("page_one"),
)
),
),
);
}
}
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:get/get_state_manager/src/simple/get_controllers.dart';
class pageOne extends StatelessWidget {
var controler=Get.find<pageOneController>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Page_one"),),
body: Container(
color: Colors.red,
child: Text("${controler.text}"),
),
);
}
}
class pageOneController extends GetxController {
String text="pageone的controller";
}
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:get/get_state_manager/src/simple/get_controllers.dart';
class pageTwo extends StatelessWidget {
var controler=Get.find<pageTwoController>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Page_two"),),
body: Container(
color: Colors.red,
child: Text("${controler.text}"),
),
);
}
}
class pageTwoController extends GetxController {
String text="pageTwo的controller";
}
参考资料:
Flutter 状态管理框架 Provider 和 Get 分析 - Flutter 中文文档 - Flutter 中文开发者网站 - Flutter