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

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

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

同じようにKotlin製DIには[Kodein](https://github.com/Kodein-Framework/Kodein-DI/)もありますがやはり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 {
            //１秒遅延
            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