私たちの経験はダガーから


注:これはKoinバージョン2.0.1で組み立てられました.詳細は公式ドキュメントを参照してください.https://insert-koin.io/

文脈
私たちは他の会社からのチームによって始められたレガシープロジェクト、他の標準、慣習、経験などを持っています.このプロジェクトは最初に依存性注入メカニズムとして短剣で設定され、モジュール化されていません.プロジェクトが成長したので、コンパイル時間もそうしました.プロジェクトをコンパイルすることが10分以上かかることができるポイントに達したとき、我々はそれについて何ができるかを見ることに決めました.

モジュール化,可能解?
最初にプロジェクトのモジュール化を考えたので、変更されたモジュールだけをプロジェクト全体ではなく再コンパイルする必要があります.これは初期コンパイル時間を解決しませんが、増分ビルドはずっと速くなります.
しかし、時間の長さを考えると、プロジェクトは、モジュールを取得しようとすると、カップリングを減らすために良いガイドラインに従うことなく、開発中だった途方もなく複雑だった.
モジュラ化することができるプロジェクトは非常に深いレベルでのリファクタが必要です.そして、顧客に新しい機能をまだ提供している間、我々はすべてをしなければなりませんでした.

短剣と注釈処理
Android Studioのビルド解析ツールのおかげで、各ビルドの時間の約40 %から50 %が注釈プロセッサによって取り上げられたことがわかりました.そして、実際にその時間のすべては短剣で取り上げられた.
我々はすでにKoinを使用して他のプロジェクトに取り組んでいたし、プロジェクトコードはすでに90 %以上のKollinであることを考えると、我々はそれが良いアイデアを1つのライブラリから他の何が起こるかを参照して移行することだと思った.最悪のシナリオでは、我々は既に知っていて、快適であった依存性注入ライブラリで終わります.

初期設定
私たちは少しずつ移行を始めた.最初のステップは、ライブラリをプロジェクトに含め、設定することでした.
private fun initKoin() {
    startKoin {
      androidContext(this@MyApp)
      modules(koinModules)
    }
  }
当初リストkoinModules 最も基本的なステートレス共通要素のインスタンスを含みます.
val koinModules = listOf(
  commonModule,
  networkModule,
  databaseModule
)
これらのモジュールには、API、ルームデータベース、ラベルマネージャ、または解析マネージャのようなものが含まれます.どんなプロジェクト機能がより大きいかより少ない範囲に必要かもしれないということ.
次のステップは、モジュール定義が正しいことを確認するためのテストを追加することでした.DaggerからKoinへの移動の主な欠点は、我々が何か間違ったことをしたならば、ダガーがすべての構築について警告するということです、他方、Koinはランタイムでだけ失敗します.幸いにもKoinでこれをテストする方法は非常に簡単ですし、私たちはバージョン(いずれかのテストや生産)をリリースする前にすべてのテストを実行するCIを持っている.
class KoinModulesTest : KoinTest {

  @get:Rule
  @ExperimentalCoroutinesApi
  val coroutineRule = CoroutineMainDispatcherRule()

  @get:Rule
  val rule: TestRule = InstantTaskExecutorRule()

  @get:Rule
  val mockProvider = MockProviderRule.create { clazz ->
    mockkClass(clazz, relaxed = true)
  }

  @Test
  fun testKoinDependencies() {
    startKoin {
      androidContext(mockk(relaxed = true))
      modules(koinModules)
    }.checkModules {
      //Here we can define the parameters for the ViewModels
      create<FeatureViewModel> { parametersOf(mockk<Foo>(), mockk<Bar>()) }
      //We can also declare mocks
      declareMock<MyService>()
     }
    }
  }
}

この単一テストで、依存関係ツリー全体をテストできます.いくつかの注意を必要とする唯一のものは、ツリー自体に外部パラメータを必要とする依存関係、例えば、フラグメントからViewModelまで渡すパラメーターです.特に、ビルド時にコードを実行する依存関係がある場合には、他の塊を宣言する必要があるかもしれません
val data = liveData {
    myService.getData(request)?.let { emit(it) }
  }
依存関係をモグラしないなら、テストは本当のサービスを呼び出すしようとする問題を与えます.
テストクラスに向かう2つの規則は、前の例のように、corrutinesで問題を回避することですgetData メソッドは不定ですが、依存性がうまく設定されていてもテストは失敗する可能性があります.
つ目は、使用するフレームワークをmokkingするKoinを定義することです.

緩やかな移行
新しい機能のためにKoinを使用する新しい開発を利用します.モジュールと依存関係を並列に作成するのは簡単ですが、これは例えば2回インスタンス化された同じapiserviceを持っているときにパフォーマンス問題を引き起こすことがありますが、ViewModelを除くと、残りのクラスはステートレスであり、プロジェクトのパフォーマンスに影響を与えません.そして、新機能として、我々は同じViewModelを2つの異なる方法で注入することの問題を抱えていない新しいビューモデルが必要です.
それぞれの新機能には新しいモジュールがあり、これは最初に定義されたモジュールのリストに追加されます.例えば、ViewModelがフラグメントからfooとbarを受け取り、フィーチャーサービスを必要とする新しい機能があるとしましょう.モジュールは以下のようになります:
val featureModule = module {
  single {
    FeatureService(get(), get())
  }
  //single<FeatureService>() if we can use reflection

  viewModel { (foo: Foo, bar: Bar) ->
    FeatureViewModule(foo, bar, get())
  }
}

実験的なKOIN機能を使用することで、サービスパラメータを定義する必要がなくなります.この機能は、すべてのケースで使用することはできませんので、反射を使用しますが、我々のケースでは、パフォーマンスへの影響は顕著ではなかったし、我々はそれを維持することを決めた.
既存の機能については、移行は似ています.私たちは、Daggerで既に定義されているKOINモジュールを定義し、モジュールリストに追加し、フラグメントの注入を変更します.
 @Inject
  lateinit var viewModel: LegacyViewModel
to
  private val viewModel: LegacyViewModel by viewModel { parametersOf(Foo()) }
Koinがこのケースで我々を提供するもう一つの利点は、注入された要素を宣言する必要はありませんlateinit VARをより明確かつ安全にするby viewModel デリゲートViewModelは怠惰な方法でインスタンス化されます.
モジュールが移行されると、注入されたクラスから@ injectコンストラクタを削除することができます.そうすれば、私たちの生産コードは、パラメータが渡される方法について何も知りません.Koinモジュール定義と我々のAndroidクラス(断片、活動)だけが依存性が注入される方法について何かを知っています.
また、私たちはDaggerFragment and DaggerAppCompatActivity Koinは拡張子で動作するので、クラスの親を変更する必要はありません.

最終結果
この移行によって我々はある程度の時間を要し、段階的に始められた.移行が完了したら、私たちはより慣用的なコードを残していました.Lateinit Varと@がなければ、すべての場所で、そして、ちょうど堅実で、CIがテストを実行して、どんなエラーも我々に警告しました.
プロジェクト全体のコードの行の量は、コードの約3000行(合計の約5 %)で減少した.3 MB以下のコードを生成すると、現在生成された唯一のコードは、部屋、buildconfig、ナビゲーションコンポーネントからです.
最も重要なのは、ビルド時間は半分以上だった.我々は5分未満のビルドに10分以上のビルドから行った.
我々は、この移行に関与している努力について、全く後悔していない.当時、それはかなりの時間投資であったが、我々が日々保存してきた時間はそれ以上の価値があった.