Flutter笔记聊一聊依赖注入(上)
1. 概述
1.1 什么是依赖注入
依赖注入并不是 Dart语言或者 Flutter框架 中独有的,它是一种重要的 编程模式,旨在更好地组织和管理类之间的依赖关系,以提高代码的可维护性、可测试性和可扩展性。这个模式在许多编程语言中得到了广泛应用,其中包括 Dart、Java、C#、JavaScript 等不同语言的各种项目中。
核心思想是 通过外部注入依赖项,而不是在类内部创建或查找依赖项。这种做法有多重好处:
- 解耦合: 依赖注入通过将依赖项传递给类,降低了各个类之间的耦合。每个类只需要知道如何使用其依赖项,而不需要知道如何创建或管理它们。这使得代码更容易维护和修改,因为对一个类的更改不会轻易影响其他类。
- 可测试性: 依赖注入使得编写单元测试变得更容易。您可以轻松地注入模拟或替代依赖项,以测试类的行为而不依赖于真实的依赖项。这有助于识别和修复问题,同时确保代码的稳定性。
- 可重用性: 通过注入依赖项,这些依赖项变得更容易重用。您可以创建通用的依赖项,然后在多个类中重复使用它们,而无需为每个类创建新的实例。
- 可扩展性: 依赖注入允许您在不更改现有类的情况下轻松添加新的功能或依赖项。这种灵活性对于扩展应用程序非常重要。
- 可维护性: 依赖注入提供了更清晰的代码结构,因为它明确显示了一个类所依赖的内容。这有助于其他开发人员理解代码并快速定位和解决问题。
1.2 依赖注入实现方法
依赖注入可以通过以下几种方式实现:
- 构造函数注入:依赖通过构造函数传递给类。
- 方法注入:依赖通过方法调用传递给类。
- 属性注入:依赖通过类的属性设置传递给类。
- 服务定位:通过一个全局的服务定位器(如Provider等)来获取依赖项。
一般情况下,构造函数注入是最常用的方式,因为它在类创建时就清晰地显示出类所需的依赖项,使代码易于理解和维护。
1.3 依赖注入在Flutter中的应用
在Flutter中,依赖注入是一种常见的模式,用于管理和组织代码中的依赖关系。Flutter并没有内置的依赖注入系统,但是有许多第三方库可以帮助我们实现依赖注入,如 Provider、Get It 和 Riverpod 等。
这些库都提供了一种方式,使我们可以在应用的任何地方访问我们的依赖项,而无需手动传递它们。这使得我们的代码更加清晰,更易于维护和测试。
例如,我们可以使用Provider库来创建一个可以在应用的任何地方访问的共享状态。这个状态可以是任何类型的对象,如数据库连接、网络服务等。我们只需要在应用的顶层提供这个状态,然后在任何需要的地方,我们都可以通过 Provider.of 方法来获取这个状态。
在接下来的章节中,我们将详细介绍如何使用这些库进行依赖注入,并提供一些最佳实践和常见问题的解决方案。
2. Provider 库与依赖注入(简介)
Provider是Flutter社区中广泛使用的一个状态管理库,它也提供了一种简单而强大的方式来实现依赖注入,它使我们的代码更加清晰,更易于维护和测试。
Provider的主要思想是 将数据模型或服务封装在 提供者(provider) 中,然后在需要的地方通过 context 获取。例如:
// 定义一个需要共享的数据模型 class Counter with ChangeNotifier { int _count = 0; int get count => _count; void increment() { _count++; notifyListeners(); } } // 在应用的顶层提供这个数据模型 void main() { runApp( ChangeNotifierProvider( create: (context) => Counter(), child: MyApp(), ), ); } // 在需要的地方获取这个数据模型 class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { var counter = Provider.of<Counter>(context); return Text('${counter.count}'); } }
我们首先定义了一个 Counter 类,它有一个 _count 字段和一个 increment 方法。然后我们在应用的顶层使用 ChangeNotifierProvider 来提供这个 Counter 对象。在需要的地方,我们可以通过 Provider.of(context) 来获取这个 Counter 对象。
这样,我们就可以在应用的任何地方访问和修改 Counter 对象,而无需手动传递它。这就是 Provider 的依赖注入功能。
在实际开发中,我们可能会有多个不同类型的数据模型或服务需要共享。在这种情况下,我们可以使用 MultiProvider 来提供多个对象。
例如,我们可能有一个用于管理用户信息的 UserModel,一个用于管理购物车信息的 CartModel,以及一个用于处理 API 调用的 ApiService 等。在这种情况下,我们可以使用 MultiProvider 来同时提供这些对象。
请看下面的代码:
void main() { runApp( MultiProvider( providers: [ ChangeNotifierProvider(create: (context) => UserModel()), ChangeNotifierProvider(create: (context) => CartModel()), Provider(create: (context) => ApiService()), ], child: MyApp(), ), ); }
我们使用 MultiProvider 同时提供了 UserModel,CartModel 和 ApiService。这样,我们就可以在应用的任何地方通过 Provider.of(context) 来获取这些对象,其中T是对象的类型。
如果我们需要在一个 Widget 中访问 UserModel,我们可以这样做:
class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { var userModel = Provider.of<UserModel>(context); // 使用userModel } }
这样,我们就可以在应用的任何地方访问和修改 UserModel, CartModel 和 ApiService,而无需手动传递它们。这就是 MultiProvider 的依赖注入功能。
3. Riverpod 库与依赖注入(简介)
Riverpod 是一个由 Flutter 社区成员开发的新的状态管理库,它也提供了依赖注入的功能。与 Provider 不同,Riverpod 不依赖于 BuildContext,这使得它更加灵活和强大。
例如:
// 定义一个需要共享的数据模型 final counterProvider = StateProvider<int>((ref) => 0); // 在需要的地方获取这个数据模型 class MyWidget extends ConsumerWidget { @override Widget build(BuildContext context, ScopedReader watch) { final counter = watch(counterProvider).state; return Text('$counter'); } } // 在需要的地方修改这个数据模型 class IncrementButton extends ConsumerWidget { @override Widget build(BuildContext context, ScopedReader watch) { return FloatingActionButton( onPressed: () => context.read(counterProvider).state++, child: Icon(Icons.add), ); } }
在上面的代码中,先定义了一个 counterProvider,它是一个 StateProvider。然后我们在需要的地方使用 watch 函数来获取这个 counterProvider 的状态。我们也可以使用 read 函数来修改这个 counterProvider**的状态。
这样,我们就可以在应用的任何地方访问和修改 counterProvider的状态,而无需手动传递它。这就是 Riverpod 的依赖注入功能。
在实际开发中,我们可能会有多个不同类型的数据模型或服务需要共享。在这种情况下,我们可以定义多个 Provider,并在需要的地方使用 watch 或 read 函数来获取或修改它们。
4. GetX库 与依赖注入(简介)
GetX 是一个功能强大的 Flutter库,它提供了状态管理、路由管理 等众多功能,其中也提供了对于 依赖注入的支持。
GetX的依赖注入系统非常简单易用,它允许我们在应用的任何地方访问我们的依赖项。
比如,我们定义一个Get服务,它继承自 GetxService:
class MyService extends GetxService { Future<MyService> init() async { // Perform async initialization return this; } }
在应用启动时,我们使用 Get.putAsync() 方法来提供这个服务:
void main() async { await Get.putAsync<MyService>(() async => await MyService().init()); runApp(MyApp()); }
4.1 从一个例子说起
在 GetX 中,我们可以使用 Get.put() 方法来提供一个依赖项。
例如,我们定义了一个 AuthService 类,它有一个 _user 字段来存储当前用户,以及 login() 和 logout() 方法来模拟登录和登出操作。我们还提供了一个 isUserLoggedIn 属性来检查用户是否已登录:
class AuthService extends GetxService { // 假设我们有一个User类来表示用户 User _user; // 获取当前用户 User get user => _user; // 模拟登录操作 Future<void> login(String username, String password) async { // 在这里,我们只是模拟登录操作,实际情况下,你可能需要调用API await Future.delayed(Duration(seconds: 1)); // 模拟网络延迟 // 假设登录成功,创建一个User对象 _user = User(username, password); } // 模拟登出操作 Future<void> logout() async { // 在这里,我们只是模拟登出操作,实际情况下,你可能需要调用API await Future.delayed(Duration(seconds: 1)); // 模拟网络延迟 // 清除用户信息 _user = null; } // 检查用户是否已登录 bool get isUserLoggedIn => _user != null; }
然后,我们可以在应用启动时使用Get.put()方法来提供这个服务:
void main() { Get.put(AuthService()); runApp(MyApp()); }
在需要的地方,我们可以使用Get.find()方法来获取这个服务:
class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { var authService = Get.find<AuthService>(); if (authService.isUserLoggedIn) { // 用户已登录,显示用户信息 } else { // 用户未登录,显示登录页面 } } }
这样,我们就可以在应用的任何地方访问和修改 AuthService,而无需手动传递它。
4.1 put、putAsync、lazyPut的区别
在GetX中,put(), putAsync(), 和 lazyPut() 方法都用于向依赖注入系统中添加对象,但它们的行为有所不同。
- put():这个方法会立即创建并添加对象。
当你调用 Get.put(Service()) 时,Service 的实例会立即被创建并添加到依赖注入系统中。 - putAsync():这个方法用于异步创建并添加对象。
当你调用 Get.putAsync(() async => await Service.create()) 时,Service 的实例会在 Service.create() 异步操作完成后被创建并添加到依赖注入系统中。 - lazyPut():这个方法用于延迟创建并添加对象。
当你调用 Get.lazyPut(() => Service()) 时,Service 的实例会在第一次通过 Get.find() 获取时被创建并添加到依赖注入系统中。
在实际项目中,应该根据你的需求选择合适的方法。如果你的对象需要进行耗时的初始化操作,你可能需要使用 putAsync()。如果你的对象只在某些特定的情况下需要,你可能需要使用 lazyPut()。在其他情况下,你可以使用 put()。
需要注意的是,如果你的服务、控制器等,它们在逻辑上如果是相互依赖的,使用 put 可能需要更多注意先后关系。因此使用 lazyPut 有时候会更加方便一些。
5. 使用Get It 库实现依赖注入(简介)
GetIt是一个简单而强大的服务定位器,也可以用于依赖注入。它允许我们在应用的任何地方访问我们的依赖项。
5.1 定义和注册依赖项
首先,我们定义一个服务类,然后在应用启动时,我们使用GetIt.I.registerSingleton()方法来注册这个服务:
void main() { GetIt.I.registerSingleton<MyService>(MyService()); runApp(MyApp()); } class MyService { fetchData() { // fetch data } }
代码中,我们在main函数中使用GetIt.I.registerSingleton()方法注册了一个MyService对象。这样,我们就可以在应用的任何地方访问这个MyService对象。
5.2 获取和使用依赖项
在GetIt中,我们可以使用 GetIt.I.get() 方法来获取一个依赖项。例如,我们可以在任何需要的地方获取并使用MyService对象:
class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { var myService = GetIt.I.get<MyService>(); myService.fetchData(); // ... } }
在上面的例子中,我们在MyWidget中使用GetIt.I.get()方法获取了MyService对象,并调用了它的fetchData方法。