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