短剣ではない

最近、多くのプログラマーがDagger2依存性注入を実装するためのライブラリを本当に気に入っています。 ボンネットの下での自明でない作業と多数の注釈のために、Daggerは長い間コミュニティに参加しています。 そして、今では、どこを見ても、多くの人がこのライブラリをほぼどこでも使用していることがわかりました。 そして、すでにDependancy Injectionはこのまさにライブラリの同義語になっています。 これは単なるライブラリですが。 はい、いいです、私は主張しません。 この記事は、King of DI Librariesの王位からのDaggerの転覆に関するものではありません。 そして、私はそのような目的のための別のツールについて話をしたいと思います-これはKoinです。



KOINとは?



Koinは、依存性注入を記述するための小さなライブラリです。 プロキシ、コード生成、およびイントロスペクションなしサービスロケーターのように機能します 。 DSLおよびKotlin言語機能を使用します。 ライブラリ自体は、Kotlinで記述されたアプリケーションで使用されることを暗示していますが、 Javaでも可能です



プロジェクトでどのように使用できるかを見てみましょう。 最初に、モジュールとすべての依存関係を実装する必要があります。



// Koin module val mainModule: Module = applicationContext { viewModel { UserProfileViewModel(get()) } viewModel { MyProfileViewModel(get()) } viewModel { DisplayUsersViewModel(get()) } viewModel { RegistrationViewModel(get()) } bean { Cicerone.create().navigatorHolder } bean { UserRepository(get(), get()) as IUsersRepository } bean { createFirestore() } } val remoteDatasourceModule = applicationContext { // provided web components bean { createOkHttpClient() } bean { createWebService<MapWebService>(get(), SERVER_URL) } }
      
      





依存関係
 fun createFirestore(): FirebaseFirestore { val store = FirebaseFirestore.getInstance() store.firestoreSettings = providesFirestoreSettings() return store } fun providesFirestoreSettings(): FirebaseFirestoreSettings = FirebaseFirestoreSettings.Builder() .setPersistenceEnabled(true) .setSslEnabled(true) .build() fun createOkHttpClient(): OkHttpClient { val httpLoggingInterceptor = HttpLoggingInterceptor() httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY return OkHttpClient.Builder() .addInterceptor(httpLoggingInterceptor) .readTimeout(TIMEOUT, TimeUnit.SECONDS) .connectTimeout(TIMEOUT, TimeUnit.SECONDS) .build() } inline fun <reified T> createWebService(okHttpClient: OkHttpClient, url: String): T { val retrofit = Retrofit.Builder() .baseUrl(url) .addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create())) .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())) .client(okHttpClient) .build() return retrofit.create(T::class.java) }
      
      







Koin DSLの内容を検討してください。



applicationContext-これは、Koinモジュールを作成するためのラムダです。 この関数はKoinモジュールを返し、Koinの各コンポーネント定義の始まりです。



factory-依存関係をファクトリコンポーネントとして提供します。 毎回新しいインスタンスを作成します。

bean-シングルトンのような依存関係を提供します。

bind-このコンポーネント定義のオプションのKotlin型バインディング。

get-コンポーネントの依存関係を許可します。 関数自体は、各クラスに必要な依存関係の種類を理解します。

context-論理コンテキストの宣言。

viewModel-別のコンパイルパッケージ「org.koin:koin-android-architecture:$ koin_version」にあるViewModelの特別な依存関係プロビジョニング



特定の例では、アーキテクチャコンポーネントを操作し、プロジェクトでViewModelを使用します。 IUserRepositoryをViewModelに注入する必要があります。 Koinを使用すると、ViewModelのコンストラクターを使用して依存関係を簡単に配信できます。



モジュールは、Application()クラスのstartKoin()関数を使用して起動する必要があります。



  override fun onCreate() { super.onCreate() startKoin(this, listOf(mainModule, remoteDatasourceModule)) }
      
      





実際、これでビューモデルをさまざまなフラグメントやアクティビティで使用するのに十分です。



 class MyActivity : AppCompatActivity(){ // Inject MyPresenter val presenter : MyPresenter by inject() // or Inject MyViewModel val myViewModel : MyViewModel by viewModel() // or Sharing ViewModel val mySharedViewModel : MySharedViewModel by sharedViewModel()
      
      





さらに、 inject()を使用て、遅延コンポーネントの初期化を行います。



怠zyなことに反対する場合、直接初期化することができます:



 val myViewModel : MyViewModel = getViewModel()
      
      





ViewModelを突然Acitivity / Fragmentと共有する必要がある場合は、sharedViewModel()を使用できます。 この時点で、AcitivityまたはFragmentにはMySharedViewModelの同じインスタンスがあります。



たとえば、カスタムビューで注入を行う必要がある場合は、 Koin Componentsが役立ちます。 KoinComponentから継承するだけで十分で、inject <>()で使用できます。 これは現在、次のクラスでは必要ありません:「アプリケーション」、「コンテキスト」、「アクティビティ」、「フラグメント」、「サービス」。



ViewModeの場合、特別なことはありませんが、コンストラクターで必要な依存関係を取得するだけです。



 // Use Repository - injected by constructor by Koin class MyViewModel(val repository : Repository) : ViewModel(){ .... }
      
      





BaseViewModelとBaseFragmentを使用した例
 open class BaseViewModel : ViewModel(), LifecycleObserver { val disposables = CompositeDisposable() val loadingStatus = MutableLiveData<Boolean>() fun addObserver(lifecycle: Lifecycle) { lifecycle.addObserver(this) } fun removeObserver(lifecycle: Lifecycle) { lifecycle.removeObserver(this) } override fun onCleared() { disposables.dispose() super.onCleared() } } abstract class BaseFragment<out T : BaseViewModel>(viewModelClass: KClass<T>) : Fragment() { protected val viewModel: T by viewModelByClass(true, viewModelClass) override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) viewModel.addObserver(lifecycle) } override fun onDestroyView() { viewModel.removeObserver(lifecycle) super.onDestroyView() } @LayoutRes protected abstract fun getLayoutRes(): Int } class UserProfileFragment : BaseFragment<UserProfileViewModel>(UserProfileViewModel::class) { ....//       viewModel }
      
      







テスト



ここではすべてが簡単です。KoinTestからテストクラスを継承する必要があり、テストクラスに直接注入できます。



試験例
 val localJavaDatasourceModule = applicationContext { provide { LocalDataSource(JavaReader()) as WeatherDatasource } } val testRxModule = applicationContext { // provided components provide { TestSchedulerProvider() as SchedulerProvider } } val testApp = weatherApp + testRxModule + localJavaDatasourceModule class ResultPresenterTest : KoinTest { val view: ResultListContract.View = mock(ResultListContract.View::class.java) val presenter: ResultListContract.Presenter by inject { mapOf(RESULT_VIEW to view) } @Before fun before() { startKoin(testApp) } @After fun after() { closeKoin() } @Test fun testDisplayWeather() { presenter.getWeather() Mockito.verify(view).displayWeather(emptyList()) } }
      
      







ロギング



Koinが実行時にスローするエラー。 したがって、すべてをテストする必要があります。

デバッグプロセス中に、Koinはログを記録し、エラーの場合、完全に理解可能なスタックトレースをスローします。



作成時のロギングの例
 04-02 12:45:23.344 ? I/KOIN: [context] create 04-02 12:45:23.377 ? I/KOIN: [module] declare Factory[class=ru.a1024bits.bytheway.viewmodel.UserProfileViewModel, binds~(android.arch.lifecycle.ViewModel)] [module] declare Factory[class=ru.a1024bits.bytheway.viewmodel.MyProfileViewModel, binds~(android.arch.lifecycle.ViewModel)] [module] declare Factory[class=ru.a1024bits.bytheway.viewmodel.DisplayUsersViewModel, binds~(android.arch.lifecycle.ViewModel)] [module] declare Factory[class=ru.a1024bits.bytheway.viewmodel.RegistrationViewModel, binds~(android.arch.lifecycle.ViewModel)] [module] declare Bean[class=ru.a1024bits.bytheway.router.LocalCiceroneHolder] [module] declare Bean[class=ru.terrakok.cicerone.NavigatorHolder] [module] declare Bean[class=ru.a1024bits.bytheway.repository.IUsersRepository] [module] declare Bean[class=com.google.firebase.firestore.FirebaseFirestore] 04-02 12:45:23.379 ? I/KOIN: [module] declare Bean[class=okhttp3.OkHttpClient] [module] declare Bean[class=ru.a1024bits.bytheway.MapWebService] [modules] loaded 10 definitions [properties] load koin.properties 04-02 12:45:23.397 ? I/KOIN: [init] Load Android features 04-02 12:45:23.566 ? I/KOIN: [Properties] no assets/koin.properties file to load [init] ~ added Android application bean reference [module] declare Bean[class=android.app.Application, binds~(android.content.Context)] 04-02 12:45:23.593 ? I/KOIN: [ViewModel] get for FragmentActivity @ ru.a1024bits.bytheway.ui.activity.SplashActivity@3cd01a24 04-02 12:45:23.594 ? I/KOIN: Resolve class[ru.a1024bits.bytheway.viewmodel.RegistrationViewModel] with Factory[class=ru.a1024bits.bytheway.viewmodel.RegistrationViewModel, binds~(android.arch.lifecycle.ViewModel)] 04-02 12:45:23.596 ? I/KOIN: Resolve class[ru.a1024bits.bytheway.repository.IUsersRepository] with Bean[class=ru.a1024bits.bytheway.repository.IUsersRepository] Resolve class[com.google.firebase.firestore.FirebaseFirestore] with Bean[class=com.google.firebase.firestore.FirebaseFirestore] 04-02 12:45:23.600 ? I/KOIN: (*) Created 04-02 12:45:23.601 ? I/KOIN: Resolve class[ru.a1024bits.bytheway.MapWebService] with Bean[class=ru.a1024bits.bytheway.MapWebService] Resolve class[okhttp3.OkHttpClient] with Bean[class=okhttp3.OkHttpClient] 04-02 12:45:23.608 ? I/KOIN: (*) Created 04-02 12:45:23.615 ? I/KOIN: (*) Created 04-02 12:45:23.616 ? I/KOIN: (*) Created (*) Created 04-02 12:45:23.749 ? I/KOIN: [ViewModel] get for FragmentActivity @ ru.a1024bits.bytheway.ui.activity.RegistrationActivity@187baf0 Resolve class[ru.a1024bits.bytheway.viewmodel.RegistrationViewModel] with Factory[class=ru.a1024bits.bytheway.viewmodel.RegistrationViewModel, binds~(android.arch.lifecycle.ViewModel)] Resolve class[ru.a1024bits.bytheway.repository.IUsersRepository] with Bean[class=ru.a1024bits.bytheway.repository.IUsersRepository] (*) Created
      
      







エラー例
  I/KOIN: Resolve class[ru.a1024bits.bytheway.viewmodel.RegistrationViewModel] with Factory[class=ru.a1024bits.bytheway.viewmodel.RegistrationViewModel, binds~(android.arch.lifecycle.ViewModel)] W/System.err: org.koin.error.NoBeanDefFoundException: No definition found to resolve type 'ru.a1024bits.bytheway.repository.UserRepository'. Check your module definition W/System.err: at org.koin.KoinContext.getVisibleBeanDefinition(KoinContext.kt:119) at org.koin.KoinContext.resolveInstance(KoinContext.kt:77) at ru.a1024bits.bytheway.koin.ModuleKt$mainModule$1$4.invoke(Module.kt:39) at ru.a1024bits.bytheway.koin.ModuleKt$mainModule$1$4.invoke(Unknown Source:2) at org.koin.core.instance.InstanceFactory.createInstance(InstanceFactory.kt:58) at org.koin.core.instance.InstanceFactory.retrieveInstance(InstanceFactory.kt:22) at org.koin.KoinContext$resolveInstance$$inlined$synchronized$lambda$1.invoke(KoinContext.kt:85) at org.koin.KoinContext$resolveInstance$$inlined$synchronized$lambda$1.invoke(KoinContext.kt:23) at org.koin.ResolutionStack.resolve(ResolutionStack.kt:23) at org.koin.KoinContext.resolveInstance(KoinContext.kt:80) at org.koin.android.architecture.ext.KoinExtKt.getWithDefinitions(KoinExt.kt:56) at org.koin.android.architecture.ext.KoinExtKt.getByTypeName(KoinExt.kt:32) at org.koin.android.architecture.ext.KoinExtKt.get(KoinExt.kt:66) at org.koin.android.architecture.ext.KoinFactory.create(KoinFactory.kt:31) at android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java:134) at android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java:102) at ru.a1024bits.bytheway.ui.activity.SplashActivity.onCreate(SplashActivity.kt:56)
      
      







おわりに



現在、バージョン0.9.1が利用可能です。これはおそらく、プロジェクトにおけるKOINの小さな配布が接続されているためです。 個人的には、使いやすさ、ViewModelでの作業機能、および遅延コンポーネントの初期化がとても気に入りました。 Koinのリリース後、Android / Kotlinの開発には素晴らしい人生が待っていると思います。 また、サービスロケーターであり、ダガーもあなたの魂を温めないためにKoinが気に入らなかった場合は、KodeinとToepickに注目してください。



All Articles