getx状态管理框架学习笔记
在这里学习了:
Flutter应用框架搭建(一)GetX集成及使用详解 - 掘金 (juejin.cn)
一、依赖添加
dependencies:
get: ^4.6.5
二、初始化
要使用 GetX 需要对 GetX 进行初始化,需将默认的 MaterialApp 替换为
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
三、状态管理
GetX 提供了两种响应式状态管理的方法:响应式变量方式和状态管理器方式。
1、响应式变量方式
定义一个响应式变量,只需在变量的末尾加上一个 .obs 就可将变量定义为响应式变量:
var count = 0.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;
获取响应式变量的值
使用的时候调用 value 即可拿到变量的值。
/////////// 注意对于 List 、Map 则不需要加 .value。///////////////
bool isLoggedValue = isLogged.value
int countValue = count.value
double numberValue = number.value
String item = items[0] //不需要.value
int value = myMap['key'] //不需要.value
String name = user.value.name
更新数据:
对于基础数据类型,只需要对 value 重新赋值即可更新数据并通过 Obx 刷新界面:
isLogged.value = true
count.value = 1
number.value = 12.0
对于其他数据类型需要调用 update 或者变量方法更新,如下:
user.update((value) {
value?.name = "123";
});
或者使用变量名方法重新赋值一个对象,比如变量名为 user 则可使用 user() 方法进行更新:
user(User(name: "abcd", age: 25));
刷新界面
在界面上使用响应式变量只需在使用变量的控件上包裹 Obx 即可实现响应式更新,即变量的值发生变化时自动刷新界面:
Obx(() => Text("${count.value}"))
数据变化监听
除了使用 Obx 实现界面数据自动刷新外,GetX 提供了多种手动方式对响应式变量进行数据变化监听,当数据发生变化时执行自定义的逻辑,比如数据变更后重新请求接口等。
- ever 当数据发生改变时触发
- everAll 和 "ever "很像,只是监听的是多个响应式变量的变化,当其中一个发生变化就会触发回调
- once 只在变量第一次被改变时被调用
- debounce 防抖,即延迟一定时间调用,且在规定时间内只有最后一次改变会触发回调。如设置时间为 1 秒,发生了3次数据变化,每次间隔500毫秒,则只有最后一次变化会触发回调。
- interval 时间间隔内只有最后一次变化会触发回调。如设置时间间隔为1秒,则在1秒内无论点击多少次都只有最后一次会触发回调,然后进入下一次的时间间隔。
使用方式:
///每次`count`变化时调用。
ever(count, (newValue) => print("$newValue has been changed"));
///只有在变量count在第一次被改变时才会被调用。
once(count, (newValue) => print("$newValue was changed once"));
///防DDos - 每当用户停止输入1秒时调用,例如。
debounce(count, (newValue) => print("debouce$newValue"), time: Duration(seconds: 1));
///忽略1秒内的所有变化,只有最后一次会触发回调。
interval(count, (newValue) => print("interval $newValue"), time: Duration(seconds: 1));
Eg. 计数器例子
class CounterPage extends StatelessWidget {
var count = 0.obs;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Counter"),
),
body: Center(
child: Obx(
() => Text("${count.value}", style: const TextStyle(fontSize: 50))),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () => count++,
),
);
}
}
2、状态管理器
GetX 还提供了使用 Controller 来管理状态,实现一个自定义 Controller 类继承自 GetxController ,Controller 中进行业务逻辑的处理,当需要改变状态数据时调用 update() 来通知数据改变。本项目在很多地方用到。
拿计数器例子来说。
controller中,使用 Controller 来管理状态,自定义Controller 类继承至GetxController。
class CounterController extends GetxController{
int count = 0;
void increment(){
count ++ ;
update();
}
}
view中,使用 GetBuilder 进行包裹,这样使用 Controller 中的数据变化时,调用 update() 后就会刷新界面控件。
GetBuilder<CounterController>(
init: CounterController(), //Controller 首次初始化
builder: (controller) {
return Text("${controller.count}", style: const TextStyle(fontSize: 50));
})
init: CounterController()只用初始化一次,之后的GetBuilder中不用写init。
Eg.计数器例子
class CounterController extends GetxController {
int count = 0;
void increment() {
count++;
update();
}
}
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Counter"),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
GetBuilder<CounterController>(
init: CounterController(),
/// 初始化 Controller
builder: (controller) {
return Text("${controller.count}",
style: const TextStyle(fontSize: 50));
}),
GetBuilder<CounterController>(
///没有进行初始化
builder: (controller) {
return Text("${controller.count}",
style: const TextStyle(fontSize: 50));
}),
],
),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
///使用 find 找到 Controller
CounterController controller = Get.find();
///调用 Controller 方法
controller.increment();
},
),
);
}
}
四、依赖
依赖管理。
解释:在GetBuilder 的 init 中初始化Controller 后,可在其他地方使用Get.find() 找到初始化的Controller。这就是依赖管理。
GetX 依赖管理可以注入任意类型的实例,并提供了多种依赖注入方式。
依赖注入
① Get.put
Get.put是 GetX 状态管理库中的一个方法,用于向 GetX 依赖注入系统中注册一个实例。依赖注入,是一种设计模式,它的目的是通过将依赖项注入到对象中来增强应用程序的可测试性、可维护性和可扩展性。
在 GetX 中,可以使用 get.put() 方法向依赖注入系统中注册一个实例。
例如,假设有一个 UserController 类用于管理用户信息,可以在应用程序启动时使用 get.put() 方法将其注册:
Get.put<UserController>(UserController());
Get.put<UserController>(UserController(), permanent: true);
Get.put<UserController>(UserController, tag: "counter");
依赖注入时除了依赖类的实例以外还可以设置额外参数:
- permanent:是否永久,默认 false 当实例不再使用时会进行销毁,true 则会一直保留
- tag:标签,用于区分同一个类不同实例。
在这个示例中,UserController 实例被注册为全局单例,可以在整个应用程序中访问该实例。可以在其他类中使用 Get.find() 方法来获取已注册的实例:
class UserProfilePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 获取 UserController 实例
final UserController controller = Get.find();
return Scaffold(
appBar: AppBar(title: Text('User Profile')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Username: ${controller.username}'),
Text('Email: ${controller.email}'),
],
),
),
);
}
}
② Get.lazyPut()
Get.lazyPut() 是 GetX 状态管理库中的一个方法,它与 Get.put() 类似,也用于向 GetX 依赖注入系统中注册一个实例。不同之处在于,Get.lazyPut() 方法是“延迟注册”,即只有在该实例第一次被引用时才会创建。
这种延迟注册的好处在于,可以避免在应用程序启动时创建不必要的实例,从而提高性能和内存使用效率。例如,如果有一个耗时的初始化过程,可以使用 Get.lazyPut() 方法来延迟创建该实例。
下面是一个示例,展示了如何使用 Get.lazyPut() 方法:
class UserController extends GetxController {
final UserRepository _repository;
UserController(this._repository);
Future<void> init() async {
// 耗时的初始化过程
}
}
void main() async {
// 延迟注册 UserController 实例
Get.lazyPut<UserController>(() => UserController(UserRepository()));
runApp(MyApp());
// 等待 UserController 初始化完成
await Get.find<UserController>().init();
}
在示例中,UserController 实例通过 Get.lazyPut() 方法进行延迟注册,只有在实例被引用时才会创建。(即第一次 find 某一个类的时候才会进行初始化) 在应用程序启动时,UserController 的初始化过程被异步执行,初始化完成后才继续执行应用程序。
需要注意的是,Get.lazyPut() 方法只会创建一个实例,并且该实例会被缓存。如果你需要每次获取实例时都重新创建一个新的实例,请使用 Get.create() 方法。
lazyPut 同样有额外参数,跟 put 基本相同。
- fenix:类似'永久',不同的是,当不使用时,实例会被丢弃,但当再次需要使用时,Get会重新创建实例
- tag:标签,用于区分同一个类不同实例。
③ Get.putAsync
putAsync 可以异步注册一个实例。用于某些实例需要异步初始化时使用,比如 SharedPreferences:
Get.putAsync<SharedPreferences>(() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('counter', 12345);
return prefs;
})
跟 put 一样,同样拥有 permanent 和 tag 参数,且作用一样。
④ Get.create
create 与 put 使用方式上基本类似,不同的是它的 permanent 默认为 true。
Get.create<CounterController>(() => CounterController());
获取实例
可以使用以下方法获取依赖的实例:
final controller = Get.find<CounterController>();
// 或者
CounterController controller = Get.find();
// 通过 tag 获取
final controller = Get.find<CounterController>("counter");
移除实例
也可以通过 delete() 方法来手动移除注入的依赖实例,大部分情况下不需要手动调用该方法,GetX 内部会自动处理,当不需要时自动移除
Get.delete<CounterController>();
五、路由管理
普通路由
- to:进入下一个界面
Get.to(CounterPage());
使用 arguments 进行参数传递:
Get.to(CounterPage(), arguments: count);
使用 arguments 方式可以传递任意类型的参数。
在下个页面获取参数:
dynamic args = Get.arguments;
- off:进入下一个界面,且导航没有返回
Get.off(CounterPage());
- offAll: 进入下一个界面并取消之前的所有路由
Get.offAll(CounterPage());
- back:返回
Get.back();
返回传参:
Get.back(result: 'success');
获取返回参数:
var data = await Get.to(CounterPage());
别名路由
首先,需要创建一个AppRouter 类用于统一配置路由映射关系:
GetPage 定义别名与页面的映射关系。
abstract class AppRouter {
static final pages = [
GetPage(
name: AppPath.login,
page: () => const LoginPage(),
),
GetPage(
name: AppPath.register,
page: () => const RegisterPage(),
),
GetPage(
name: AppPath.forgetPassword,
page: () => const ForgetPasswordPage(),
),
GetPage(
name: AppPath.setProfile,
page: () => const SetProfilePage(),
),
];
}
同时也可以创建AppPath 类用于统一配置路由名称
abstract class AppPath {
static const String home = '/';
static const String login = '/login';
static const String register = '/register';
static const String forgetPassword = '/forgetPassword';
static const String setProfile = '/setProfile';
static const String startLive = '/startLive';
static const String liveRoom = '/liveRoom';
static const String aboutUs = '/aboutUs';
static const String editProfile = '/EditProfile';
static const String setMySignature = '/setMySignature';
static const String myFollowing = '/myFollowing';
static const String feedback = '/feedback';
static const String sessionList = '/sessionList';
}
然后在 GetMaterialApp 进行initialRoute 和 getPages 的配置,即初始页面和路由映射集合:
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Flutter Demo',
initialRoute: AppPath.home,
getPages: AppRouter.pages,
theme: ThemeData(
primarySwatch: Colors.blue,
)
);
}
}
- 路由跳转:
Get.toNamed(RouteGet.login);
- 路由传参:
Get.toNamed(RouteGet.login, arguments: {"name":"aaaa"});
也可以直接在路由别名后面跟参数,类似于 Url get 传参的方式:
Get.toNamed("/NextScreen?device=phone&id=354&name=Enzo");
- 接收参数:
- 通过 arguments 进行传参,在下个页面接收参数直接使用 Get.arguments 获取到传递过来的参数:
dynamic args = Get.arguments;
使用别名后 Url 传递参数的方式,使用 Get.parameters 获取参数:
Get.parameters['device']
Binding
Bindings 是 GetX 状态管理库中的一个概念,它用于在页面或路由生命周期中注册和注销依赖项,以确保资源的正确释放和管理。
在 GetX 中,每个页面或路由都可以有一个对应的 Binding 类,该类负责管理页面或路由所需要的所有依赖项。当通过 GetX 路由进入页面时,会自动调用 dependencies 方法, 可以在这里进行依赖关系的注册等。
别名路由举例:
class LoginBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => LoginController());
}
}
然后在GetPage 中进行绑定
GetPage(
name: AppPath.login,
page: () => const LoginPage(),
binding: LoginBinding(),
),
普通路由使用:
Get.to(CounterPage(), binding: CounterBinding());