Edited at

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