目录
1.前言
2.GetX 依赖管理概述
1.GetX 依赖管理的基本概念
2.与其他依赖管理工具的比较
3. 基础依赖注入
1.Get.put
2.Get.lazyPut
3.Get.putAsync
4.高级依赖注入
1.使用Get.create
2.依赖生命周期管理
5. 参考资料
1.前言
今天这篇博客主要介绍Getx的三大功能之一的依赖管理。依赖管理是软件开发中的一个关键部分,尤其是在复杂应用中。它帮助开发者管理应用中的各种依赖,确保依赖的实例化和生命周期管理变得更加简单和高效。
2.GetX 依赖管理概述
1.GetX 依赖管理的基本概念
GetX 提供了一种简单且高效的依赖注入方式,通过少量代码即可实现依赖的注入、管理和访问。它主要通过Get.put、Get.lazyPut、Get.putAsync和Get.create等方法来实现依赖管理。
2.与其他依赖管理工具的比较
Provider:Provider 是 Flutter 官方推荐的依赖注入和状态管理工具。它需要较多的样板代码,使用起来相对复杂。
Riverpod:Riverpod 是 Provider 的增强版,提供了更多的功能和更好的性能,但学习曲线较陡。
GetX:GetX 简单易用,提供了更多的功能,如路由管理和状态管理,适合各种规模的项目。
3. 基础依赖注入
1.Get.put
Get.put 是 GetX 提供的一个方法,用于将一个实例注入到依赖管理系统中。它的主要作用是创建一个新的实例,并将其注册为依赖,供整个应用程序的其他部分使用。使用 Get.put 可以方便地管理和访问依赖实例,避免手动管理实例的生命周期。
它的使用场景如下:
1.全局单例:需要在整个应用程序中共享的依赖。
2.初始化即创建:需要在应用启动时立即创建的依赖。
我们使用Get.put的时候,还可以配置一些选项:
- dependency:要注入的依赖实例
- tag:依赖实例的可选标签,用于区分相同类型的不同实例。
- permanent:是否将依赖设置为永久存在,即使不再使用也不会被销毁。
我们可以通过一个例子说明Get.put的用法。
在这个例子中,我们在首页操作计时器,然后在收藏页面调用HomeController中的计时器的点击次数。
图1.Get.put用法
我们来看看如何实现这个功能。
1.首先我们创建一个控制器,它包含计数逻辑。
class HomeController extends GetxController {
var count = 0.obs;
void increment() {
count++;
}
}
2.在应用启动时注入依赖
在 main 方法中使用 Get.put 将 CounterController 注入到依赖管理系统中。
void main() {
// 注入依赖
Get.put(HomeController());
runApp(const MyApp());
}
3.在页面中使用依赖
完整代码:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class DependencyManagerMain extends StatefulWidget {
const DependencyManagerMain({super.key});
@override
State<DependencyManagerMain> createState() => _DependencyManagerMainState();
}
class _DependencyManagerMainState extends State<DependencyManagerMain>
with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
// 初始化 TabController
_tabController = TabController(length: 3, vsync: this);
}
@override
void dispose() {
// 销毁 TabController
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: TabBarView(
controller: _tabController,
children: [
HomePage(),
FavoritesTab(),
const SettingsTab(),
],
),
bottomNavigationBar: TabBar(
controller: _tabController,
tabs: const [
Tab(icon: Icon(Icons.home), text: "首页"),
Tab(icon: Icon(Icons.star), text: "收藏"),
Tab(icon: Icon(Icons.settings), text: "设置"),
],
),
);
}
}
class HomeController extends GetxController {
var count = 0.obs;
void increment() {
count++;
}
}
class HomePage extends StatelessWidget {
HomePage({super.key});
final HomeController controller = Get.find<HomeController>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('首页'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 使用 Obx 来监听 count 的变化
Obx(() => Text(
'Count: ${controller.count}',
style: const TextStyle(fontSize: 20),
)),
const SizedBox(height: 20),
ElevatedButton(
onPressed: controller.increment,
child: const Text('Increment'),
),
],
),
),
);
}
}
class FavoritesTab extends StatelessWidget {
FavoritesTab({super.key});
final HomeController controller = Get.find<HomeController>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('收藏'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 使用 Obx 来监听 count 的变化
Obx(() => Text(
'点击次数: ${controller.count}',
style: const TextStyle(fontSize: 20),
)),
const SizedBox(height: 20),
],
),
),
);
}
}
class SettingsTab extends StatelessWidget {
const SettingsTab({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text('Settings Tab Content'),
);
}
}
2.Get.lazyPut
可以懒加载一个依赖,这样它只有在使用时才会被实例化。这对于计算代价高的类来说非常有用,或者如果你想在一个地方实例化几个类(比如在Bindings类中),而且你知道你不会在那个时候使用这个类。
Get.lazyPut方法用于延迟创建依赖实例,只有在第一次使用时才会创建。
这里就不写demo了,感兴趣的话,可以参考上一个例子中的demo写Demo测试一下。
///只有当第一次使用Get.find<ApiMock>时,ApiMock才会被调用。
Get.lazyPut<ApiMock>(() => ApiMock());Get.lazyPut<FirebaseAuth>(
() {
// ... some logic if needed
return FirebaseAuth();
},
tag: Math.random().toString(),
fenix: true
)Get.lazyPut<Controller>( () => Controller() )
3.Get.putAsync
如果你想注册一个异步实例,你可以使用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() )
我们还可以设置如下参数:
Get.putAsync<S>(
// 必备:一个将被执行的异步方法,用于实例化你的类。
AsyncInstanceBuilderCallback<S> builder,// 可选:和Get.put()一样,当你想让同一个类有多个不同的实例时,就会用到它。
// 必须是唯一的
String tag,// 可选:与Get.put()相同,当你需要在整个应用程序中保持该实例的生命时使用。
// 默认值为false
bool permanent = false
)
4.GetView、GetxView 和GetxController
4.高级依赖注入
1.使用Get.create
Get.create方法用于每次请求时创建一个新的依赖实例,适用于需要多次创建的依赖。
void main() {
Get.create<MyController>(() => MyController());
runApp(MyApp());
}
2.依赖生命周期管理
GetX 提供了三种依赖生命周期管理模式:
Permanent:永久存在,直到应用关闭。
Singleton:默认模式,实例在第一次创建后存在于整个应用生命周期内。
Transient:每次请求时创建新的实例。
模块化依赖注入:
在大型项目中,可以将依赖注入模块化,便于管理和维护。
class AppBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<MyController>(() => MyController());
}
}void main() {
runApp(GetMaterialApp(
initialBinding: AppBinding(),
home: MyApp(),
));
}
5.依赖管理中的常见问题及解决方案
1.依赖未能正确注入或找到
例如在下面的例子中,我在首页调用Get.find的方法,但是想相应的实例方法却没有被创建。
CounterController counterController = Get.find();
图2.依赖未能正确注入或找到
控制台日志输出如下:
图3.控制台输入日志
解决的方法也很简单,就是在调用之前确保相应的GetxController被实例化。
例如我们可以在app启动之前,实例化相应的控制器或者初始化的时候就绑定相应的GetxController。
图3.全局绑定GetxController
2.实例化过程中的性能问题
在使用 GetX 时,如果不小心,可能会引入性能问题。例如,不恰当地更新整个 UI 而不是局部更新,或者频繁地创建和销毁控制器。下面是一个示例,展示了如何使用 GetX 并说明可能的性能问题,以及如何优化这些问题。
在下面的代码中,PerformancelssueExample类展示了可能的性能问题。每次计数器值变化时,整个 Column 会被重建,导致性能下降。
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
home: HomePage(),
);
}
}
class HomeController extends GetxController {
var counter = 0.obs;
void increment() => counter.value++;
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomeController controller = Get.put(HomeController());
return Scaffold(
appBar: AppBar(
title: Text('GetX Performance Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
GetX<HomeController>(
builder: (controller) {
return Text(
'${controller.counter.value}',
style: Theme.of(context).textTheme.headline4,
);
},
),
PerformanceIssueExample(),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: controller.increment,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class PerformanceIssueExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomeController controller = Get.find();
return Column(
children: [
Obx(() {
print("Rebuilding entire Column");
return Column(
children: [
for (int i = 0; i < 100; i++)
Text('Item $i'),
Text('Counter: ${controller.counter.value}'),
],
);
}),
ElevatedButton(
onPressed: controller.increment,
child: Text('Increment Counter'),
),
],
);
}
}
优化建议:
- 避免在可观察值变化时重建整个组件树。
-
将变化范围限制在最小的部件中,以减少重建次数
-
使用 GetX 或 Obx 只在必要时更新 UI
3.依赖生命周期管理不当导致的内存泄漏
在使用 GetX 时,内存管理是一个重要的考虑因素。如果不正确地管理控制器和依赖项,可能会导致内存泄漏。下面是一个示例,说明 GetX 内存管理中的常见问题及其解决方案。
1.示例代码
假设我们有一个简单的计数器应用,每次进入一个新页面都会创建一个新的控制器实例,但未正确释放旧的控制器实例,这会导致内存泄漏。
以下面代码为例:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class CounterController extends GetxController {
var count = 0.obs;
void increment() => count++;
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Page'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Get.to(CounterPage());
},
child: Text('Go to Counter Page'),
),
),
);
}
}
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 不推荐的做法:每次进入页面时创建新的控制器实例
final CounterController controller = Get.put(CounterController());
return Scaffold(
appBar: AppBar(
title: Text('Counter Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
GetX<CounterController>(
builder: (_) {
return Text('Count: ${controller.count.value}');
},
),
ElevatedButton(
onPressed: controller.increment,
child: Text('Increment'),
),
],
),
),
);
}
}
在这个示例中,每次导航到 CounterPage 时,都会创建一个新的 CounterController 实例,但未正确释放旧的实例,这会导致内存泄漏。
2.解决方案
为了避免内存泄漏,可以使用 Get.put 或 Get.lazyPut 在合适的地方创建和释放控制器实例,并使用 Get.delete 方法在不需要时释放控制器实例。
修改后的代码如下:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class CounterController extends GetxController {
var count = 0.obs;
void increment() => count++;
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Page'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Get.to(CounterPage());
},
child: Text('Go to Counter Page'),
),
),
);
}
}
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 推荐的做法:使用 Get.put 只创建一个控制器实例,直到不再需要
final CounterController controller = Get.put(CounterController());
return Scaffold(
appBar: AppBar(
title: Text('Counter Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
GetX<CounterController>(
builder: (_) {
return Text('Count: ${controller.count.value}');
},
),
ElevatedButton(
onPressed: controller.increment,
child: Text('Increment'),
),
],
),
),
);
}
@override
void dispose() {
// 当页面销毁时,释放控制器实例
Get.delete<CounterController>();
super.dispose();
}
}
修改之后的实例中:
1.正确使用Get.put:在 CounterPage 页面创建时,只创建一个 CounterController 实例。
2.使用Get.put和Get.lazyPut:根据需要创建和管理控制器实例。
3.释放控制器实例:在页面销毁时,通过 Get.delete<CounterController>() 方法释放控制器实例,防止内存泄漏。
6. 参考资料
1.Getx官方文档