在Flutter开发中,GetX是一个非常强大的库,特别是它的依赖注入(DI)功能。依赖注入不仅简化了状态管理和服务的获取,还使得应用更加模块化,易于测试。本文通过实例深入讲解GetX依赖注入的应用。
GetX的依赖注入:
Get.put()- 应用于全局服务或控制器,例如主题管理器,这些服务在整个应用的生命周期中都是必要的。
Get.lazyPut()(搭配Get.find())- 适用于特定页面或模块的控制器,例如用户个人信息页面,当这些页面被访问时,相关的控制器才会被初始化。
Get.putAsync()- 适用于需要异步操作来初始化的服务,例如数据库服务,这些服务在应用启动时进行异步初始化。
一、说啥呢,先从例子开始
一个简单的例子:控制器注入
假设我们有一个计数器应用,其中有一个CounterController控制器来管理计数状态。
dart
class CounterController extends GetxController {
var count = 0.obs;
void increment() {
count++;
}
}
注册控制器
我们在某个界面初始化时注册此控制器。
dart
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
Get.put(CounterController()); // 注册控制器
return Scaffold(
appBar: AppBar(title: Text("Counter App")),
body: CounterWidget(),
);
}
}
使用控制器
在CounterWidget中,我们可以使用Get.find()来获取控制器实例。
dart
class CounterWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final CounterController controller = Get.find();
return Obx(() => Text('Count: ${controller.count}'));
}
}
带参数的控制器
让我们来看一个稍微复杂的例子。假设我们需要根据用户ID获取用户信息,我们有一个UserController和一个User模型。
dart
class User {
final int id;
final String name;
User(this.id, this.name);
}
class UserController extends GetxController {
final User user;
UserController(this.user);
}
注册控制器并传递参数
我们在注册UserController时传递一个User实例。
dart
class UserProfileScreen extends StatelessWidget {
final User user;
UserProfileScreen(this.user);
@override
Widget build(BuildContext context) {
Get.put(UserController(user)); // 注册时传递参数
return Scaffold(
appBar: AppBar(title: Text("User Profile")),
body: UserWidget(),
);
}
}
获取和使用带参数的控制器
在UserWidget中,我们再次通过Get.find()获取UserController实例,并使用它来展示用户信息。
dart
class UserWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final UserController controller = Get.find();
return Text('User: ${controller.user.name}');
}
}
使用Bindings来管理依赖
GetX还提供了Bindings类来集中管理依赖。这对于大型应用来说非常有用,因为它可以将依赖的注册和配置与UI逻辑分离。
创建绑定
我们创建一个CounterBinding来注册CounterController。
dart
class CounterBinding extends Bindings {
@override
void dependencies() {
Get.put(CounterController());
}
}
使用绑定
在路由中,我们指定CounterBinding来在页面构建前完成依赖注入。
dart
GetPage(
name: '/home',
page: () => HomeScreen(),
binding: CounterBinding(),
);
二、准备,321,上场景
在 GetX 中,依赖注入主要通过几种不同的方式来实现,每种方式都有其特定的用途和场景。这些方式包括 Get.put(), Get.lazyPut(), 和 Get.putAsync()。了解它们之间的区别对于合理利用 GetX 的依赖注入机制至关重要。
第一种 Get.put()
Get.put() 是 GetX 中最直接的依赖注入方法。它会立即实例化所提供的对象,并将其存储在 Get 的内部存储中,以便之后可以在应用的任何地方通过 Get.find() 检索。
dart
Get.put(CounterController());
场景示例1:认证服务
在应用中,通常需要一个全局的认证服务来处理登录、登出和用户身份验证。
dart
class AuthService {
Future<bool> isLoggedIn() async {
// 检查登录状态
}
Future<void> login(String username, String password) {
// 登录逻辑
}
// 更多认证相关方法...
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
Get.put(AuthService()); // 在应用启动时立即创建
return MaterialApp(home: HomeScreen());
}
}
场景示例2:购物车
对于一个电商应用,购物车是一个典型的需要全局访问的对象。
dart
class CartService {
final List<Item> _items = [];
void addItem(Item item) {
_items.add(item);
}
// 更多购物车相关方法...
}
class ShoppingCartPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final cartService = Get.find<CartService>();
return Scaffold(
// 构建购物车页面
);
}
}
页面示例3:主页面和导航
假设有一个主页面 MainPage,它包含一个底部导航栏,该导航栏在整个应用中都是可见的,并控制着多个子页面。
dart
class MainController extends GetxController {
// 控制底部导航栏索引
var tabIndex = 0.obs;
}
class MainPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 控制器立即初始化
Get.put(MainController());
return Scaffold(
body: Obx(() => IndexedStack(
index: Get.find<MainController>().tabIndex.value,
// 子页面...
)),
bottomNavigationBar: BottomNavigationBar(
// 底部导航逻辑...
),
);
}
}
使用场景:
- 当你需要确保对象在注册时立即创建。
- 当该对象在整个应用生命周期中都可能被使用,或者它的生命周期与整个应用相同。
- 适用于核心服务或全局状态管理。
第二种 Get.lazyPut()
Get.lazyPut() 则更加灵活。它不会立即创建依赖,而是提供了一个"工厂函数",只有在第一次使用 Get.find() 查找依赖时,该依赖才会被实例化。
dart
Get.lazyPut(() => CounterController());
场景示例1:订单详情
在电商应用中,仅当用户打开订单详情页面时,才需要创建用于管理订单信息的控制器。
dart
class OrderDetailsController {
final String orderId;
OrderDetailsController(this.orderId);
Future<Order> getOrderDetails() {
// 获取订单详情
}
}
class OrderDetailsPage extends StatelessWidget {
final String orderId;
OrderDetailsPage(this.orderId);
@override
Widget build(BuildContext context) {
Get.lazyPut(() => OrderDetailsController(orderId));
final controller = Get.find<OrderDetailsController>();
return Scaffold(
// 构建订单详情页面
);
}
}
场景示例2:动态内容
假设一个应用中有一个动态内容区域,其内容根据用户选择的选项卡而变化。
dart
class DynamicContentController {
// 用于管理动态内容的逻辑
}
class DynamicContentPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
Get.lazyPut(() => DynamicContentController());
final controller = Get.find<DynamicContentController>();
return Scaffold(
// 构建含有动态内容的页面
);
}
}
使用场景:
- 当依赖项可能不会立即在应用中使用时,延迟其创建可以提高应用启动的效率。
- 节省资源,只有在真正需要时才创建对象。
- 当对象的创建成本较高或只在特定条件下使用时。
第三种 Get.putAsync()
Get.putAsync() 用于异步依赖注入。它允许你在依赖项的创建过程中执行异步操作(例如网络请求),然后将异步创建的对象注册为依赖。
dart
Get.putAsync<SomeService>(() async {
final service = await SomeService.getInstance();
return service;
});
使用场景:
- 当对象的创建依赖于异步操作(如从数据库加载数据,或进行网络请求)时。
- 适用于初始化过程需要等待异步操作完成的服务或控制器。
场景示例1:异步加载的用户配置
在一些应用中,用户配置可能需要从远程服务器异步加载。
dart
class UserSettingsService {
Future<UserSettings> loadUserSettings() async {
// 异步加载用户设置
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
Get.putAsync<UserSettingsService>(() async {
final service = UserSettingsService();
await service.loadUserSettings();
return service;
});
return MaterialApp(home: HomeScreen());
}
}
场景示例2:数据库初始化
某些应用需要在启动时异步初始化数据库。
dart
class DatabaseService {
Future<void> initDatabase() async {
// 数据库初始化逻辑
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
Get.putAsync<DatabaseService>(() async {
final dbService = DatabaseService();
await dbService.initDatabase();
return dbService;
});
return MaterialApp(home: HomeScreen());
}
}
总结
Get.put()应用于全局服务或控制器,例如主题管理器,这些服务在整个应用的生命周期中都是必要的。Get.lazyPut()适用于特定页面或模块的控制器,例如用户个人信息页面,当这些页面被访问时,相关的控制器才会被初始化。Get.putAsync()适用于需要异步操作来初始化的服务,例如数据库服务,这些服务在应用启动时进行异步初始化。
三、GetX 中结合使用 GetView 和 GetBuilder,走两步?
在 GetX 中结合使用 GetView 和 GetBuilder 来构建页面并结合控制器。这是 GetX 中一种常见的模式,用于连接 UI 和逻辑层。
先来一份代码,
view
DART
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'index.dart';
class TestPagePage extends GetView<TestPageController> {
const TestPagePage({Key? key}) : super(key: key);
// 主视图
Widget _buildView(TestPageController controller) {
return const Center(
child: InkWell(
onTap: controller.doSomeThing(),
child: Text("TestPagePage"),) ,
);
}
@override
Widget build(BuildContext context) {
return GetBuilder<TestPageController>(
init: TestPageController(),
id: "test_page",
builder: (_) {
return Scaffold(
appBar: AppBar(title: const Text("test_page")),
body: SafeArea(
child: _buildView(controller),
),
);
},
);
}
}
controller
DART
import 'package:get/get.dart';
class TestPageController extends GetxController {
TestPageController();
_initData() {
update(["test_page"]);
}
void onTap() {}
// @override
// void onInit() {
// super.onInit();
// }
@override
void onReady() {
super.onReady();
_initData();
}
doSomeThing() {
print("doSomeThing ing...");
}
// @override
// void onClose() {
// super.onClose();
// }
}
三下除以五,框框看完。
GetView 的使用
TestPagePage 类继承自 GetView<TestPageController>,这意味着这个页面将直接关联到 TestPageController 类型的控制器。GetView 提供了一个 controller 属性,它可以直接访问已经注册的 TestPageController 实例。
GetBuilder 的使用
GetBuilder 是一个 GetX 的组件,它用于在控制器的某些属性更改时重建其子部件。这里,GetBuilder<TestPageController> 包裹了 Scaffold,它允许 Scaffold 和其子部件在 TestPageController 更新时重新构建。
在这个例子中,init: TestPageController() 表示创建一个 TestPageController 的实例。如果你希望控制器在不同页面之间共享或有更长的生命周期,你可以通过 Get.put() 或其他 GetX 注入方法在适当的地方注册这个控制器。
为什么不用Get.put() 或 Get.lazyPut()之类也能使用controller?
在上面代码中,TestPageController 是通过 GetBuilder 的 init 属性初始化的。这种方式会在 GetBuilder 创建时实例化 TestPageController,这就是为什么即使没有显式调用 Get.put() 或 Get.lazyPut(),您也能在 buildView 里直接使用 controller。
结合依赖注入
在 GetX 中,依赖注入通常在应用的顶层(如 main 函数)或页面初始化前进行。考虑到 TestPagePage 页面依赖于 TestPageController,我们有几种方式来处理控制器的依赖注入:
使用 Get.put()
如果 TestPageController 被多个页面共享,或者它的生命周期应该与整个应用相同,可以在应用初始化时注册它。
dart
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
Get.put(TestPageController()); // 注册控制器
return MaterialApp(
home: TestPagePage(),
);
}
}
使用 Get.lazyPut()
如果 TestPageController 只在 TestPagePage 中使用,或者你想要延迟其实例化直到实际需要时,可以使用 Get.lazyPut()。
dart
class TestPagePage extends GetView<TestPageController> {
const TestPagePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
Get.lazyPut(() => TestPageController()); // 懒加载控制器
return GetBuilder<TestPageController>(
id: "test_page",
builder: (_) {
return Scaffold(
// 构建页面UI
);
},
);
}
}
对比对比: GetBuilder直接init,对比使用Get.put()、Get.lazyput()的区别、场景选择
使用 GetBuilder 的 init 属性初始化 TestPageController 是 GetX 中的一种便捷的依赖注入方式,它在特定的场景下非常有用。了解这种方式的特点有助于决定何时使用它,以及它与 Get.put() 和 Get.lazyPut() 的区别和应用场景。下面我们来分析这些不同的点。
GetBuilder 的 init 属性初始化
特点
- 局部作用域 :控制器的生命周期与
GetBuilder组件绑定,通常局限于当前页面或视图。 - 自动实例化 :当
GetBuilder被创建时,它会自动实例化控制器,无需在全局或其他位置显式注册。 - 页面或视图专用:适合于控制器仅在特定页面或组件中使用的情况,与全局状态或跨多个页面共享的状态相对独立。
适用场景
- 当控制器仅用于当前页面或视图,并且不需要在其他地方访问时。
- 当希望控制器的生命周期与页面或组件的生命周期紧密相连时。
Get.put() 和 Get.lazyPut()
特点
- 全局或跨页面作用域:控制器可以在整个应用范围内或跨多个页面中使用。
- 显式注册 :需要在适当的位置(如
main函数或页面初始化前)显式注册控制器。 - 控制器的复用和管理:更适合于需要跨页面复用的控制器或需要更精细管理其生命周期的场合。
适用场景
- 当控制器需要在应用的多个部分中使用,或其状态需要在多个页面间共享时,适合使用
Get.put()。 - 当控制器的创建成本较高,或只有在特定情况下才需要初始化时,适合使用
Get.lazyPut()。
选择区分
在选择 init 方式与 Get.put() / Get.lazyPut() 之间的区分时,关键在于考虑控制器的使用范围和生命周期管理的需求:
- 如果控制器是页面或视图专用的,并且你希望它的生命周期紧随当前页面,使用
GetBuilder的init方式是合适的。 - 如果控制器需要在多个页面间共享或其生命周期超出单个页面,那么
Get.put()或Get.lazyPut()更为合适,它们提供了更大的灵活性和控制能力,尤其是在复杂的应用结构中。
总结
在这个 TestPagePage 页面示例中,通过 GetView 和 GetBuilder 的结合使用,GetX 实现了对 TestPageController 的依赖注入和有效的状态管理。这样的结构不仅提高了代码的可读性和可维护性,还使得状态管理更为简洁和高效。同时,通过选择适当的依赖注入方式,可以根据控制器在应用中的使用情况灵活管理其生命周期。
当StatefulWidget遇上GetX,结合依赖注入
view
DART
import 'package:get/get.dart';
class TestPageController extends GetxController {
TestPageController();
_initData() {
update(["test_page"]);
}
void onTap() {}
// @override
// void onInit() {
// super.onInit();
// }
@override
void onReady() {
super.onReady();
_initData();
}
doSomeThing() {
print("doSomeThing ing...");
}
// @override
// void onClose() {
// super.onClose();
// }
}
controller
DART
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'index.dart';
class TestFulPage extends StatefulWidget {
const TestFulPage({Key? key}) : super(key: key);
@override
State<TestFulPage> createState() => _TestFulPageState();
}
class _TestFulPageState extends State<TestFulPage>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
return const _TestFulViewGetX();
}
}
class _TestFulViewGetX extends GetView<TestFulController> {
const _TestFulViewGetX({Key? key}) : super(key: key);
// 主视图
Widget _buildView() {
return const Center(
child: Text("TestFulPage"),
);
}
@override
Widget build(BuildContext context) {
return GetBuilder<TestFulController>(
init: TestFulController(),
id: "test_ful",
builder: (_) {
return Scaffold(
appBar: AppBar(title: const Text("test_ful")),
body: SafeArea(
child: _buildView(),
),
);
},
);
}
}
来吧。
让我们更详细地分析 TestFulPage 和 TestPagePage 的设计,以及 TestFulPage 如何与依赖注入结合。
TestFulPage 和 TestPagePage 的比较
-
使用
StatefulWidget(TestFulPage):- 场景选择 :
StatefulWidget是当你需要更多的控制页面生命周期或保持页面状态时的理想选择。例如,使用AutomaticKeepAliveClientMixin可以保留页面状态,这在像TabBarView中的页面保持状态时非常有用。 - 优势:允许更精细地管理页面的状态,如初始化时的数据加载、页面刷新、或资源释放。
- 场景选择 :
-
使用
Getx结合StatelessWidget(TestPagePage):- 场景选择 :适用于状态管理主要依赖于
GetxController,页面本身不需要维护自己的状态。例如,当页面的所有状态都可以通过控制器来管理,且页面本身不需要响应生命周期事件时。 - 优势 :代码更简洁,易于维护,依赖于
Getx的强大状态管理和依赖注入能力,同时减少了 Flutter 自身状态管理的复杂性。
- 场景选择 :适用于状态管理主要依赖于
TestFulPage 如何结合依赖注入
在 TestFulPage 中,_TestFulViewGetX 类继承自 GetView<TestFulController>,这为其提供了对 TestFulController 的直接访问。这里的关键是如何结合使用 Getx 的依赖注入特性:
-
局部控制器实例化 :在
GetBuilder<TestFulController>中使用init属性来实例化TestFulController。这个实例化是局部的,控制器的生命周期与GetBuilder组件的生命周期相绑定。 -
依赖注入的选择:
- 使用
init属性初始化控制器意味着控制器是为当前组件服务的,适用于页面或组件专用的控制器。 - 如果
TestFulController需要在多个页面间共享,或需要在不同页面间保持状态,应该考虑在应用的其他部分(如在main函数中)使用Get.put()或Get.lazyPut()进行全局注册。
- 使用
-
控制器与页面的关系 :通过
GetBuilder,控制器可以控制页面的重建,例如在数据更改时通过调用update()触发页面的重绘。
总结
TestFulPage 的设计利用了 StatefulWidget 的生命周期管理和状态保持功能,同时结合了 Getx 提供的简洁的状态管理和依赖注入能力。选择使用 StatefulWidget 还是结合 Getx 的 StatelessWidget 取决于页面的具体需求,特别是在页面状态保持和生命周期管理方面的需求。
在 TestFulPage 的例子中,通过 GetBuilder 的 init 属性实现控制器的局部依赖注入,为特定组件提供了专用的控制器实例。这种设计在需要为特定页面或组件封装独立的逻辑和状态时非常有用,同时它还提供了 Getx 的所有优点,包括简洁的状态管理和更新机制。
来例子
如果 TestFulController 需要在多个页面间共享,或需要在不同页面间保持状态,应该考虑在应用的其他部分(如在 main 函数中)使用 Get.put() 或 Get.lazyPut() 进行全局注册。
针对这一段,有必要说一说。
当你需要在多个页面间共享 TestFulController 或在这些页面间保持其状态时,全局注册控制器变得尤为重要。下面我将通过一个具体的场景来阐释如何利用 Get.put() 或 Get.lazyPut() 进行全局注册,并将其结合到 TestFulPage 和 TestFulController 中。
场景说明
假设我们正在开发一个社交媒体应用,其中 TestFulController 负责管理用户的个人信息和设置。这些信息需要在应用的多个页面(比如个人信息页面、设置页面、用户的帖子列表页面等)中访问和修改。
全局注册 TestFulController
在这种场景下,TestFulController 应该在应用启动时就被全局注册,以便在整个应用中都能够访问和使用。
main.dart - 应用入口
dart
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 全局注册 TestFulController
Get.put(TestFulController());
return MaterialApp(
home: TestFulPage(),
);
}
}
test_ful_controller.dart - 控制器
dart
class TestFulController extends GetxController {
var userInfo = UserInfo().obs;
var userSettings = UserSettings().obs;
void updateUserSettings(UserSettings settings) {
userSettings.value = settings;
}
// 其他用户信息和设置的方法...
}
在多个页面中使用 TestFulController
现在 TestFulController 已经全局注册,你可以在任何页面中使用 Get.find() 来获取控制器的实例并使用它。
user_profile_page.dart - 用户个人信息页面
dart
class UserProfilePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 获取全局的 TestFulController 实例
final controller = Get.find<TestFulController>();
return Scaffold(
appBar: AppBar(title: Text("User Profile")),
body: Obx(() {
// 使用 TestFulController 管理的数据
return Text("User Name: ${controller.userInfo.value.name}");
}),
);
}
}
user_settings_page.dart - 用户设置页面
dart
class UserSettingsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 获取全局的 TestFulController 实例
final controller = Get.find<TestFulController>();
return Scaffold(
appBar: AppBar(title: Text("Settings")),
body: Obx(() {
// 使用 TestFulController 管理的数据
return SwitchListTile(
title: Text("Enable Notifications"),
value: controller.userSettings.value.notificationsEnabled,
onChanged: (value) {
controller.updateUserSettings(
controller.userSettings.value.copyWith(notificationsEnabled: value)
);
},
);
}),
);
}
}
总结
在这个场景中,通过在 main.dart 中使用 Get.put(),TestFulController 被全局注册并在整个应用的生命周期内保持活跃。这种方式使得控制器能够在多个页面中共享,并保持其状态。无论用户在哪个页面更改了设置或个人信息,这些更改都会被 TestFulController 管理,并在所有页面中保持同步。这提供了一种高效、简洁的方式来管理跨多个页面的共享状态。
就写到这里吧。
记得点赞。