Reuse OkHttp instance

This commit is contained in:
Kirill Kamakin
2021-11-20 22:23:51 +03:00
parent a6e948ca6b
commit dd9c302729
7 changed files with 63 additions and 33 deletions

View File

@@ -6,7 +6,6 @@ import androidx.room.TypeConverters
import gq.kirmanak.mealient.data.impl.RoomTypeConverters import gq.kirmanak.mealient.data.impl.RoomTypeConverters
import gq.kirmanak.mealient.data.recipes.db.RecipeDao import gq.kirmanak.mealient.data.recipes.db.RecipeDao
import gq.kirmanak.mealient.data.recipes.db.entity.* import gq.kirmanak.mealient.data.recipes.db.entity.*
import javax.inject.Singleton
@Database( @Database(
version = 1, version = 1,
@@ -14,7 +13,6 @@ import javax.inject.Singleton
exportSchema = false exportSchema = false
) )
@TypeConverters(RoomTypeConverters::class) @TypeConverters(RoomTypeConverters::class)
@Singleton
abstract class AppDb : RoomDatabase() { abstract class AppDb : RoomDatabase() {
abstract fun recipeDao(): RecipeDao abstract fun recipeDao(): RecipeDao
} }

View File

@@ -9,19 +9,35 @@ import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import gq.kirmanak.mealient.data.impl.OkHttpBuilder
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient
import javax.inject.Singleton
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
interface AppModule { interface AppModule {
companion object { companion object {
@Provides @Provides
fun createDb(@ApplicationContext context: Context): AppDb { @Singleton
return Room.databaseBuilder(context, AppDb::class.java, "app.db").build() fun createDb(@ApplicationContext context: Context): AppDb =
} Room.databaseBuilder(context, AppDb::class.java, "app.db").build()
@Provides @Provides
fun createSharedPreferences(@ApplicationContext context: Context): SharedPreferences { @Singleton
return PreferenceManager.getDefaultSharedPreferences(context) fun createSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context)
@Provides
@Singleton
fun createOkHttp(okHttpBuilder: OkHttpBuilder): OkHttpClient =
okHttpBuilder.buildOkHttp()
@Provides
@Singleton
fun createJson(): Json = Json {
coerceInputValues = true
ignoreUnknownKeys = true
} }
} }
} }

View File

@@ -1,18 +1,29 @@
package gq.kirmanak.mealient.data.auth.impl package gq.kirmanak.mealient.data.auth.impl
import gq.kirmanak.mealient.data.auth.AuthStorage
import kotlinx.coroutines.runBlocking
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Response import okhttp3.Response
import timber.log.Timber
import javax.inject.Inject
const val AUTHORIZATION_HEADER = "Authorization" const val AUTHORIZATION_HEADER = "Authorization"
class AuthOkHttpInterceptor(token: String) : Interceptor { class AuthOkHttpInterceptor @Inject constructor(
private val headerValue = "Bearer $token" private val authStorage: AuthStorage
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
val newRequest = chain.request() Timber.v("intercept() called with: chain = $chain")
.newBuilder() val token = runBlocking { authStorage.getToken() }
.addHeader(AUTHORIZATION_HEADER, headerValue) Timber.d("intercept: token = $token")
.build() val request = if (token.isNullOrBlank()) {
return chain.proceed(newRequest) chain.request()
} else {
chain.request()
.newBuilder()
.addHeader(AUTHORIZATION_HEADER, "Bearer $token")
.build()
}
return chain.proceed(request)
} }
} }

View File

@@ -8,12 +8,14 @@ import okhttp3.logging.HttpLoggingInterceptor
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class OkHttpBuilder @Inject constructor() { class OkHttpBuilder @Inject constructor(
fun buildOkHttp(token: String?): OkHttpClient { private val authOkHttpInterceptor: AuthOkHttpInterceptor
Timber.v("buildOkHttp() called with: token = $token") ) {
fun buildOkHttp(): OkHttpClient {
Timber.v("buildOkHttp() called")
val builder = OkHttpClient.Builder() val builder = OkHttpClient.Builder()
if (BuildConfig.DEBUG) builder.addNetworkInterceptor(buildLoggingInterceptor()) if (BuildConfig.DEBUG) builder.addNetworkInterceptor(buildLoggingInterceptor())
if (token != null) builder.addNetworkInterceptor(AuthOkHttpInterceptor(token)) builder.addNetworkInterceptor(authOkHttpInterceptor)
return builder.build() return builder.build()
} }

View File

@@ -4,26 +4,23 @@ import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFact
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import retrofit2.Retrofit import retrofit2.Retrofit
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ExperimentalSerializationApi @ExperimentalSerializationApi
class RetrofitBuilder @Inject constructor(private val okHttpBuilder: OkHttpBuilder) { class RetrofitBuilder @Inject constructor(
private val json by lazy { private val okHttpClient: OkHttpClient,
Json { private val json: Json
coerceInputValues = true ) {
ignoreUnknownKeys = true fun buildRetrofit(baseUrl: String): Retrofit {
} Timber.v("buildRetrofit() called with: baseUrl = $baseUrl")
}
fun buildRetrofit(baseUrl: String, token: String? = null): Retrofit {
Timber.v("buildRetrofit() called with: baseUrl = $baseUrl, token = $token")
val contentType = "application/json".toMediaType() val contentType = "application/json".toMediaType()
val converterFactory = json.asConverterFactory(contentType) val converterFactory = json.asConverterFactory(contentType)
return Retrofit.Builder() return Retrofit.Builder()
.baseUrl(baseUrl) .baseUrl(baseUrl)
.client(okHttpBuilder.buildOkHttp(token)) .client(okHttpClient)
.addConverterFactory(converterFactory) .addConverterFactory(converterFactory)
.build() .build()
} }

View File

@@ -38,7 +38,7 @@ class RecipeDataSourceImpl @Inject constructor(
val baseUrl = checkNotNull(authRepo.getBaseUrl()) { "Base url is null" } val baseUrl = checkNotNull(authRepo.getBaseUrl()) { "Base url is null" }
val token = checkNotNull(authRepo.getToken()) { "Token is null" } val token = checkNotNull(authRepo.getToken()) { "Token is null" }
Timber.d("requestRecipes: baseUrl = $baseUrl, token = $token") Timber.d("requestRecipes: baseUrl = $baseUrl, token = $token")
val retrofit = retrofitBuilder.buildRetrofit(baseUrl, token) val retrofit = retrofitBuilder.buildRetrofit(baseUrl)
val createdService = retrofit.create(RecipeService::class.java) val createdService = retrofit.create(RecipeService::class.java)
_recipeService = createdService _recipeService = createdService
createdService createdService

View File

@@ -2,8 +2,10 @@ package gq.kirmanak.mealient.data.impl
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.HiltAndroidTest
import gq.kirmanak.mealient.data.auth.AuthStorage
import gq.kirmanak.mealient.data.auth.impl.AUTHORIZATION_HEADER import gq.kirmanak.mealient.data.auth.impl.AUTHORIZATION_HEADER
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_TOKEN import gq.kirmanak.mealient.test.AuthImplTestData.TEST_TOKEN
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_URL
import gq.kirmanak.mealient.test.MockServerTest import gq.kirmanak.mealient.test.MockServerTest
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
@@ -17,16 +19,20 @@ class OkHttpBuilderTest : MockServerTest() {
@Inject @Inject
lateinit var subject: OkHttpBuilder lateinit var subject: OkHttpBuilder
@Inject
lateinit var authStorage: AuthStorage
@Test @Test
fun `when token null then no auth header`() { fun `when token null then no auth header`() {
val client = subject.buildOkHttp(null) val client = subject.buildOkHttp()
val header = sendRequestAndExtractAuthHeader(client) val header = sendRequestAndExtractAuthHeader(client)
assertThat(header).isNull() assertThat(header).isNull()
} }
@Test @Test
fun `when token isn't null then auth header contains token`() { fun `when token isn't null then auth header contains token`() {
val client = subject.buildOkHttp(TEST_TOKEN) authStorage.storeAuthData(TEST_TOKEN, TEST_URL)
val client = subject.buildOkHttp()
val header = sendRequestAndExtractAuthHeader(client) val header = sendRequestAndExtractAuthHeader(client)
assertThat(header).isEqualTo("Bearer $TEST_TOKEN") assertThat(header).isEqualTo("Bearer $TEST_TOKEN")
} }