給 Mobile 開發者的 Flutter 入門 - 如何使用 GetIt 和 Injectable 來做 DI

TLDR

這篇主要是介紹如何透過 GetIt 和 Injectable 兩個套件,來完成 Flutter Dependency Injection 依賴注入(簡稱 DI)的功能。 這邊不多說明 DI 的概念,如果有興趣可以參考之前寫的文章 Android 的依賴注入框架-Dagger2(一)。有稍微提到什麼是 DI。

在過往 Android 開發中,一開始早期使用 Dagger2,到後來的 Koin,以及最近的 Hilt,所以在這邊也會把做法以及相似的部分做一個比較,也能讓 Android 開發者更容易理解。

以下我們會用一個簡單的例子來說明如何使用 GetIt 和 Injectable 來完成 DI 的功能。

GetIt

首先可以到 GetIt 的網站 GetIt 去將 GetIt 套件安裝至專案中,這邊不做多餘的贅述。GetIt 是一個簡單的 Service Locator,可以用來管理依賴的實例,這邊先來看一下 GetIt 的基本用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import 'package:get_it/get_it.dart';

final getIt = GetIt.instance;

void setup() {
<!-- DI provider -->
getIt.registerSingleton<CounterService>(CounterService());
}

void main() {
setup();
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {

final counterService = getIt<CounterService>();
return MaterialApp(
home: Scaffold(
body: Center(
child: Text(counterService.count.toString()),
),
),
);
}
}

這邊我們先註冊一個 CounterService 的實例,然後在 Appbuild 方法中,透過 getIt<CounterService>() 來取得 CounterService 的實例,這樣就完成了一個簡單的 DI。

在這個簡單的例子中,可以想像一下我們的 setup 是一個 DI Provider,在 App 初始化之前提供了許多我們需要的物件實例,然後在 App 初始化的時候,透過 getIt 來取得這些實例,讓我們的代碼可以更加地解除耦合,那 get_it 也提供了更多的功能,像是我們在範例中,使用 registerSingleton 來註冊一個 Singleton 的實例,但有時候我們不是一開始就需要這些物件實例,而是希望等到後續有相關的業務代碼呼叫到的時候再去實例化,這時候我們可以使用 registerLazySingleton 來註冊一個 Lazy Singleton 的實例。

但是你會發現,當我們如果有很多的依賴,這樣的寫法會讓我們的 setup 方法變得很大,所以這時候我們可以使用 Injectable 來幫助我們更好的管理我們的依賴。這邊也會將加入 Injectable 來改寫上面的例子,以及比較一下跟 Koin, Heilt 的差異。

Injectable

Injectable 是一個 透過 Annotation 來生成 GetIt 的 相關代碼,讓我們可以更好的管理我們的依賴,這邊先來看一下 Injectable 的基本用法。

首先可以到 Injectable 的網站 Injectable 去將 Injectable 套件安裝至專案中,這邊不做多餘的贅述。

首先我們建立一個 injection.dart

1
2
3
4
5
6
7
8
9
10
11
12
import 'package:injectable/injectable.dart';
import 'inject.config.dart';

final getIt = GetIt.instance;

@InjectableInit(
initializerName: 'init',
preferRelativeImports: true,
asExtension: false,
)

Future<void> configureDependencies() => $init(getIt);

然後我們建立一個 service_module.dart

1
2
3
4
5
6
7
import 'package:injectable/injectable.dart';

@module
abstract class ServiceModule {
@singleton
CounterService get counterService => CounterService();
}

接下來其實只要執行一下 flutter pub run build_runner build 就可以生成相關的代碼,然後我們就可以在我們的 App 中使用了。

1
2
3
4
5
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await configureDependencies();
runApp(MyApp());
}

生成後的代碼我們可以發現,在 injection.config.dart 中,會生成一個 GetItInjectableX 的 Extension,這個 Extension 會幫助我們初始化我們的依賴。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
extension GetItInjectableX on _i1.GetIt {
// initializes the registration of main-scope dependencies inside of GetIt
Future<_i1.GetIt> init({
String? environment,
_i2.EnvironmentFilter? environmentFilter,
}) async {
final gh = _i2.GetItHelper(
this,
environment,
environmentFilter,
);
final serviceModule = _$ServiceModule();
gh.singleton<_i3.CounterService>(serviceModule.counterService);
return this;
}
}

class _$ServiceModule extends _i7.ServiceModule {}

熟悉 koin 跟 Hilt 的朋友應該會發現,這邊的 @module@singletonkoin / Hlit 的 provider module 實作非常類似。

koin

1
2
3
4
5
6
7
8
9
val myModule = module {
single { CounterService() }
}

fun main() {
startKoin {
modules(myModule)
}
}

Hilt

1
2
3
4
5
6
7
@Module
@InstallIn(SingletonComponent::class)
object ServiceModule {
@Singleton
@Provides
fun provideCounterService() = CounterService()
}

由上面的案例可能不覺得 Injectable 帶來什麼好處,那我們接下來來看一下這個複雜一點的案例,如果我們只使用 GetIt,我有一個 ServiceA 以及 ServiceB,然後 ServiceB 依賴 ServiceA,這時候我們可以這樣寫。

1
2
3
4
5
6
7
8
9
10
11

class ServiceA {}

class ServiceB {
ServiceB(ServiceA serviceA);
}

void setup() {
getIt.factory<ServiceA>(() => ServiceA());
getIt.factory<ServiceB>(ServiceB(getIt<ServiceA>()));
}

如果今天有更多依賴,我們必須手動去維護這些關係在 getIt 的 setup 中,透過 Injectable,可以透過註解快速的生成這些依賴關係。

1
2
3
4
5
6
7
8
@module
abstract class ServiceModule {
@singleton
ServiceA get serviceA => ServiceA();

@singleton
ServiceB getServiceB(ServiceA serviceA) => ServiceB(serviceA);
}

其他 Injectable 還有更多類似 Koin/Hlit 的功能,像是 Binding abstract class to implementation class、name、scope 等相關功能, 這邊就不多做贅述,有興趣的朋友可以參考 Injectable 的官方文件。

以上就是一些在 Flutter 中使用 GetIt 以及 Injectable 來完成 DI 的功能,這邊也有提到一些跟 Android 開發中的 DI 框架的比較,希望這篇文章對大家有所幫助。