LoginSignup
6
12

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-11-08

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

6
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
12