Android
Kotlin
DI
Dagger
Koin

Kotlin DI:Koinを使ってAndroid ApiのMockを実現してApiサーバーなしでもアプリ開発を進める

Koinの話:
https://github.com/InsertKoinIO/koin

Android Dagger/Dagger2の書き方がかなりしんどかったのでやめました。
特にKotlin時代にはもっとスマートな書き方があるのではないかと色々調べたところKoinに出会い。

同じようにKotlin製DIにはKodeinもありますがやはりKoinの方が綺麗に書ける。

App.ktでは使うObjectのInject声明をします。

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin(this, listOf(appModule, viewRepoModule, otherModule,
                    if(!BuildConfig.DEBUG) apiModule else mockApiModule))
    }
}
  • if(true) apiModule else mockApiModuleでローカルなどからApiのMock化が簡単にできる。
  • apiModuleとmockApiModuleの二つがあり、必要に応じてモジュール読み込みます。

各モジュールはDIModules.ktファイルにまとめる

/**
 * DIModules.kt ファイル
**/

val appModule: Module = module {
    single { AppDatabase.buildDatabase(androidApplication()) }
    single { (get() as AppDatabase).you1Dao() }
    single { (get() as AppDatabase).you2Dao() }
    single { (get() as AppDatabase).you3Dao() }
    single { (get() as AppDatabase).you4Dao() }
}

val apiModule : Module = module {
    single { ApiClient().setup(androidApplication()) }
    single { (get() as ApiClient).retrofitBuilder().baseUrl(BASE_URL).build() } //Retrofit
    single { (get() as Retrofit).create(You1Api::class.java) }
    single { (get() as Retrofit).create(You2Api::class.java) }
    single { (get() as Retrofit).create(You3Api::class.java) }
    single { (get() as Retrofit).create(You4Api::class.java) }
    single { (get() as Retrofit).create(You5Api::class.java) }
    single { (get() as Retrofit).create(You6Api::class.java) }
    single { (get() as Retrofit).create(LoginApi::class.java) }
}

val mockApiModule : Module = module {
    single { ApiClient().setup(androidApplication()) }
    single { (get() as ApiClient).retrofitBuilder().baseUrl(BASE_URL).build() } //Retrofit
    single { You1ApiMock() as You1Api }
    single { You2ApiMock() as You2Api }
    single { You3ApiMock() as You3Api }
    single { You4ApiMock() as You4Api }
    single { You5ApiMock() as You5Api }
    single { You6ApiMock() as You6Api }
    single { LoginApiMock() as LoginApi }
}

val viewRepoModule : Module = module {
    viewModel { You1ViewModel(get()) }
    viewModel { You2ViewModel(get()) }
    viewModel { You3ViewModel(get()) }
    viewModel { You4ViewModel(get(),get()) }
    viewModel { You5ViewModel(get()) }
}

val otherModule : Module = module {
    single { You1Repository(get(),get()) }
    single { You2Repository(get()) }
    single { You3Repository(get()) }
    single { You4Repository(get(), get(), get()) }
    single { You5Repository(get(), get(), get()) }
}
  • factoryは使う時に常に新しいインスタンスを生成します。
  • singleはsingletonで一個のインスタンスを使い回します。
  • get()で自動的にKoinの中で声明したところから適切なパラメタをとってきます。例えばYou1Repository(get(), get())のクラスは下記のようです。

class You1Repository(private val you1Api: You1Api, private val you1Dao: You1Dao)

  • 実際のサーバーAPIと通信したい場合はapiModuleを使う、ローカルjsonで動きを確認したい場合はmockApiModuleを使います。

MockApiを作る

もしAPIがまだない、先にローカルjsonファイルを読み込んでテストしたい場合は
RetrofitのApiはinterfaceになっているのでそれをimplementationしたクラスでローカルのjsonファイルを呼びます。

class You1ApiMock : You1Api {
    override fun getSomeData(type: Int): Deferred<YouData> {
        DeferredReadJson.getJsonData("mock/json/YouJsonFile.json")
    }
}

interface UserPostApi {
    @GET("/api/users/list")
    fun getSomeData(@Query("type"): Deferred<YouData>
}
  • jsonファイルはassetes/mock/jsonの中におきます。

遅延のあるJson Responseを作る

ローカルのJsonファイルを呼び時もNetworkで呼んだ場合と同じように遅延させたいと思うのでcouroutineで遅延を掛けます。

object DeferredReadJson : KoinComponent {
    val context: Context by inject()

    inline fun <reified T> getJsonData(jsonFileName: String) : Deferred<T> {
        if(!NetworkUtil.isNetworkConnect()) {
            throw ConnectException()
        }
        return GlobalScope.async {
            //1秒遅延
            delay(1000)
            val data: T = context.loadAssetsJsonFile(jsonFileName)
            return@async data
        }
    }
}
  • KoinComponentでcontextをInjectします。
  • inline reifiedでどのタイプのresponseでも対応できます。

Koinによる自動Injection

使う時はconstructorによるinjectionで自動的にインスタンスを取得します。
もしmockApiModuleが声明されたらyou1ApiはYou1ApiMockのインスタンスになります。

class You1Repository(private val you1Api: You1Api, private val you1Dao: You1Dao) {
...
}

class You1ViewModel(private val you1Repository: You1Repository) : ViewModel() {
...
}

class MainActivity : AppCompatActivity()
    private val you1ViewModel: You1ViewModel by viewModel()
...
}
  • MainActivityでYou1ViewModelをDI -> You1ViewModelはさらにYou1RepositoryをDI -> You1RepositoryはさらにYou1ApiとYou1DaoをDI.(You1ApiはapiModuleもしくはmockApiModuleから生成)

Retrofitのapi responseをKotlinのDeferredで使うためにこちらのadapterを使っています。
https://github.com/JakeWharton/retrofit2-kotlin-coroutines-adapter

jsonパーズにはmoshiを使っています。
https://github.com/square/moshi