From bdf218cd858da6cfb1bbfa76ae0bf91c5c2b72bc Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Tue, 18 Jul 2023 17:51:05 +0530 Subject: [PATCH] refactor(api): rework Retrofit DI and expose LobstersSearchApi --- .../msfjarvis/claw/api/injection/ApiModule.kt | 48 --------- .../claw/api/injection/RetrofitModule.kt | 98 +++++++++++++++++++ .../claw/core/injection/JsonModule.kt | 6 +- 3 files changed, 102 insertions(+), 50 deletions(-) delete mode 100644 api/src/main/kotlin/dev/msfjarvis/claw/api/injection/ApiModule.kt create mode 100644 api/src/main/kotlin/dev/msfjarvis/claw/api/injection/RetrofitModule.kt diff --git a/api/src/main/kotlin/dev/msfjarvis/claw/api/injection/ApiModule.kt b/api/src/main/kotlin/dev/msfjarvis/claw/api/injection/ApiModule.kt deleted file mode 100644 index 20f829e1..00000000 --- a/api/src/main/kotlin/dev/msfjarvis/claw/api/injection/ApiModule.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright © 2021-2023 Harsh Shandilya. - * Use of this source code is governed by an MIT-style - * license that can be found in the LICENSE file or at - * https://opensource.org/licenses/MIT. - */ -package dev.msfjarvis.claw.api.injection - -import com.deliveryhero.whetstone.app.ApplicationScope -import com.slack.eithernet.ApiResultCallAdapterFactory -import com.slack.eithernet.ApiResultConverterFactory -import com.squareup.anvil.annotations.ContributesTo -import dagger.Module -import dagger.Provides -import dev.msfjarvis.claw.api.LobstersApi -import javax.inject.Qualifier -import okhttp3.OkHttpClient -import retrofit2.Converter -import retrofit2.Retrofit -import retrofit2.create - -@Module -@ContributesTo(ApplicationScope::class) -object ApiModule { - @Provides - fun provideRetrofit( - client: OkHttpClient, - converterFactories: Set<@JvmSuppressWildcards Converter.Factory>, - @BaseUrl baseUrl: String, - ): Retrofit { - return Retrofit.Builder() - .client(client) - .baseUrl(baseUrl) - .addConverterFactory(ApiResultConverterFactory) - .addCallAdapterFactory(ApiResultCallAdapterFactory) - .apply { converterFactories.forEach(this::addConverterFactory) } - .build() - } - - @Provides - fun provideApi(retrofit: Retrofit): LobstersApi { - return retrofit.create() - } - - @Provides @BaseUrl fun provideBaseUrl(): String = LobstersApi.BASE_URL -} - -@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class BaseUrl diff --git a/api/src/main/kotlin/dev/msfjarvis/claw/api/injection/RetrofitModule.kt b/api/src/main/kotlin/dev/msfjarvis/claw/api/injection/RetrofitModule.kt new file mode 100644 index 00000000..8fa103e7 --- /dev/null +++ b/api/src/main/kotlin/dev/msfjarvis/claw/api/injection/RetrofitModule.kt @@ -0,0 +1,98 @@ +/* + * Copyright © 2023 Harsh Shandilya. + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +package dev.msfjarvis.claw.api.injection + +import com.deliveryhero.whetstone.app.ApplicationScope +import com.slack.eithernet.ApiResultCallAdapterFactory +import com.slack.eithernet.ApiResultConverterFactory +import com.squareup.anvil.annotations.ContributesTo +import dagger.Module +import dagger.Provides +import dagger.multibindings.IntKey +import dagger.multibindings.IntoMap +import dev.msfjarvis.claw.api.LobstersApi +import dev.msfjarvis.claw.api.LobstersSearchApi +import dev.msfjarvis.claw.api.converters.SearchConverter +import javax.inject.Qualifier +import okhttp3.OkHttpClient +import retrofit2.CallAdapter +import retrofit2.Converter +import retrofit2.Retrofit +import retrofit2.create + +/** + * Ideally the multibindings used here would only use [dagger.multibindings.IntoSet], but its lack + * of ordering guarantees means that we roll a die on each app launch that [Converter]s and + * [CallAdapter]s are in the correct order to be able to deserialize responses. Thus, the module + * uses [IntoMap] with [IntKey]s to fake the presence of a fixed order by sorting on the key of the + * injected [Map]s when injecting them into [Retrofit]. + */ +@Module +@ContributesTo(ApplicationScope::class) +object RetrofitModule { + @Provides + fun provideRetrofit( + client: OkHttpClient, + converterFactories: Map, + callAdapterFactories: Map, + @BaseUrl baseUrl: String, + ): Retrofit { + return Retrofit.Builder() + .client(client) + .baseUrl(baseUrl) + .apply { converterFactories.toSortedMap().values.forEach(this::addConverterFactory) } + .apply { callAdapterFactories.toSortedMap().values.forEach(this::addCallAdapterFactory) } + .build() + } + + @Provides + @SearchApi + fun provideSearchApiRetrofit( + client: OkHttpClient, + @SearchApi converterFactories: List<@JvmSuppressWildcards Converter.Factory>, + callAdapterFactories: Map, + @BaseUrl baseUrl: String, + ): Retrofit { + return Retrofit.Builder() + .client(client) + .baseUrl(baseUrl) + .apply { converterFactories.forEach(this::addConverterFactory) } + .apply { callAdapterFactories.toSortedMap().values.forEach(this::addCallAdapterFactory) } + .addConverterFactory(SearchConverter.Factory) + .build() + } + + @Provides fun provideApi(retrofit: Retrofit): LobstersApi = retrofit.create() + + @Provides + fun provideSearchApi(@SearchApi retrofit: Retrofit): LobstersSearchApi = retrofit.create() + + @Provides + @IntKey(0) + @IntoMap + fun provideApiResultConverter(): Converter.Factory = ApiResultConverterFactory + + @Provides + @IntKey(0) + @IntoMap + fun provideApiResultCallAdapter(): CallAdapter.Factory = ApiResultCallAdapterFactory + + @Provides + @SearchApi + fun provideConverters(): List<@JvmSuppressWildcards Converter.Factory> = + listOf( + ApiResultConverterFactory, + SearchConverter.Factory, + ) + + @Provides @BaseUrl fun provideBaseUrl(): String = LobstersApi.BASE_URL +} + +@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class BaseUrl + +/** Internal Dagger [Qualifier] to identify dependencies exclusive to [LobstersSearchApi]. */ +@Qualifier @Retention(AnnotationRetention.RUNTIME) private annotation class SearchApi diff --git a/core/src/main/kotlin/dev/msfjarvis/claw/core/injection/JsonModule.kt b/core/src/main/kotlin/dev/msfjarvis/claw/core/injection/JsonModule.kt index 433a3130..8910fb48 100644 --- a/core/src/main/kotlin/dev/msfjarvis/claw/core/injection/JsonModule.kt +++ b/core/src/main/kotlin/dev/msfjarvis/claw/core/injection/JsonModule.kt @@ -11,7 +11,8 @@ import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFact import com.squareup.anvil.annotations.ContributesTo import dagger.Module import dagger.Provides -import dagger.multibindings.IntoSet +import dagger.multibindings.IntKey +import dagger.multibindings.IntoMap import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonNamingStrategy @@ -24,7 +25,8 @@ import retrofit2.Converter object JsonModule { @Provides - @IntoSet + @IntKey(1) + @IntoMap fun provideJsonConverterFactory(json: Json): Converter.Factory { val contentType = "application/json".toMediaType() return json.asConverterFactory(contentType)