Skip to content

Commit

Permalink
Merge pull request #25 from 6QuizOnTheBlock/and/feat#19-retrofit
Browse files Browse the repository at this point in the history
And/feat#19 retrofit
  • Loading branch information
ghddbwns9808 authored Apr 29, 2024
2 parents b9dedfc + daae6ef commit 1268d32
Show file tree
Hide file tree
Showing 18 changed files with 577 additions and 1 deletion.
4 changes: 4 additions & 0 deletions android/data/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
alias(libs.plugins.sixkids.android.library)
alias(libs.plugins.sixkids.android.hilt)
}

android {
Expand All @@ -10,5 +11,8 @@ dependencies {
implementation(projects.core.model)
implementation(projects.domain)

implementation(libs.bundles.retrofit)
implementation(libs.datastore)

testImplementation(libs.junit)
}
14 changes: 14 additions & 0 deletions android/data/src/main/java/com/sixkids/data/api/TokenService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.sixkids.data.api

import com.sixkids.data.model.request.RefreshTokenRequest
import com.sixkids.data.model.response.TokenResponse
import com.sixkids.data.network.ApiResult
import retrofit2.http.Body
import retrofit2.http.POST

interface TokenService {
@POST("members/token")
suspend fun refreshToken(
@Body refreshTokenRequest: RefreshTokenRequest
): ApiResult<TokenResponse>
}
29 changes: 29 additions & 0 deletions android/data/src/main/java/com/sixkids/data/di/DataStoreModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.sixkids.data.di

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStoreFile
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object DataStoreModule {

private const val USER_PREFERENCES = "user_preferences"

@Provides
@Singleton
fun provideUserPreferenceDataStore(@ApplicationContext context: Context)
: DataStore<Preferences> {
return PreferenceDataStoreFactory.create(
produceFile = { context.preferencesDataStoreFile(USER_PREFERENCES) }
)
}
}
151 changes: 151 additions & 0 deletions android/data/src/main/java/com/sixkids/data/di/NetworkModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package com.sixkids.data.di

import com.sixkids.data.network.ApiResultCallAdapterFactory
import com.sixkids.data.network.RefreshTokenInterceptor
import com.sixkids.data.network.TokenAuthenticator
import com.sixkids.data.network.TokenInterceptor
import com.squareup.moshi.Moshi
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import java.util.concurrent.TimeUnit
import javax.inject.Qualifier
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

private const val BASE_URL = "base_url"
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class PublicOkHttpClient

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthOkHttpClient

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class RefreshOkHttpClient

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class PublicRetrofit

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthRetrofit

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class RefreshRetrofit


@Provides
@Singleton
fun moshi(): Moshi {
return Moshi.Builder().build()
}

@Provides
@Singleton
@PublicOkHttpClient
fun provideUnauthenticatedOkHttpClient(): OkHttpClient {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
return OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build()
}

@Provides
@Singleton
@AuthOkHttpClient
fun provideAccessOkHttpClient(
tokenInterceptor: TokenInterceptor,
tokenAuthenticator: TokenAuthenticator
): OkHttpClient {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
return OkHttpClient.Builder()
.authenticator(tokenAuthenticator)
.addInterceptor(tokenInterceptor)
.addInterceptor(loggingInterceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build()
}

@Provides
@Singleton
@RefreshOkHttpClient
fun provideRefreshOkHttpClient(
refreshTokenInterceptor: RefreshTokenInterceptor
): OkHttpClient {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
return OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.addInterceptor(refreshTokenInterceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build()
}

@Provides
@Singleton
@PublicRetrofit
fun providePublicRetrofit(
moshi: Moshi,
@PublicOkHttpClient okHttpClient: OkHttpClient
): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addCallAdapterFactory(ApiResultCallAdapterFactory())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.client(okHttpClient)
.build()
}

@Provides
@Singleton
@AuthRetrofit
fun provideAuthRetrofit(
moshi: Moshi,
@AuthOkHttpClient okHttpClient: OkHttpClient
): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addCallAdapterFactory(ApiResultCallAdapterFactory())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.client(okHttpClient)
.build()
}

@Provides
@Singleton
@RefreshRetrofit
fun provideRefreshRetrofit(
moshi: Moshi,
@RefreshOkHttpClient okHttpClient: OkHttpClient
): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addCallAdapterFactory(ApiResultCallAdapterFactory())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.client(okHttpClient)
.build()
}

}
18 changes: 18 additions & 0 deletions android/data/src/main/java/com/sixkids/data/di/RepositoryModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.sixkids.data.di

import com.sixkids.data.repository.TokenRepositoryImpl
import com.sixkids.domain.repository.TokenRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent

@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {

@Binds
abstract fun bindTokenRepository(
tokenRepository: TokenRepositoryImpl
): TokenRepository
}
22 changes: 22 additions & 0 deletions android/data/src/main/java/com/sixkids/data/di/ServiceModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.sixkids.data.di

import com.sixkids.data.api.TokenService
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import retrofit2.Retrofit
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object ServiceModule {

@Singleton
@Provides
fun provideTokenService(
@NetworkModule.RefreshRetrofit retrofit: Retrofit
): TokenService {
return retrofit.create(TokenService::class.java)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.sixkids.data.model.request

data class RefreshTokenRequest(
val accessToken: String,
val refreshToken: String

)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.sixkids.data.model.response

data class TokenResponse(
val accessToken: String,
val refreshToken: String
)
19 changes: 19 additions & 0 deletions android/data/src/main/java/com/sixkids/data/network/ApiResult.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.sixkids.data.network

sealed class ApiResult<out T> {
data class Success<T>(val data: T) : ApiResult<T>()

data class Error(val exception: Throwable) : ApiResult<Nothing>()

val isSuccess : Boolean
get() = this is Success<T>

val isFailure : Boolean
get() = this is Error

val getOrNull : T? =
when(this) {
is Success -> data
else -> null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.sixkids.data.network

import okhttp3.Request
import okio.Timeout
import org.json.JSONObject
import retrofit2.Call
import retrofit2.CallAdapter
import retrofit2.Callback
import retrofit2.Response
import java.io.IOException
import java.lang.NullPointerException
import java.lang.reflect.Type

class ApiResultCallAdapter<T>(
private val successType: Type
): CallAdapter<T, Call<ApiResult<T>>> {
override fun responseType(): Type = successType

override fun adapt(call: Call<T>): Call<ApiResult<T>> =
ApiResultCall(call)
}

private class ApiResultCall<T>(
private val delegate: Call<T>
): Call<ApiResult<T>> {

override fun enqueue(callback: Callback<ApiResult<T>>) {
delegate.enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
val body = response.body()

if (response.isSuccessful) {
if (body != null) {
// ์ •์ƒ์ ์ธ response
callback.onResponse(
this@ApiResultCall,
Response.success(ApiResult.Success(body))
)
} else {
callback.onResponse(
this@ApiResultCall,
Response.success(ApiResult.Error(NullPointerException("response body is null")))
)
}
} else {
val errorBodyString = response.errorBody()?.string()
val message = try {
if (!errorBodyString.isNullOrBlank()) {
JSONObject(errorBodyString).getString("message")
} else {
""
}
} catch (e: Exception) {
""
}
callback.onResponse(
this@ApiResultCall,
Response.success(ApiResult.Error(UlbanException(response.code(),message)))
)
}

}

override fun onFailure(call: Call<T>, t: Throwable) {
val error = if (t is IOException) {
ApiResult.Error(IOException("Network error", t))
} else {
ApiResult.Error(t)
}
callback.onResponse(this@ApiResultCall, Response.success(error))
}
})
}

override fun clone(): Call<ApiResult<T>> = ApiResultCall(delegate.clone())

override fun execute(): Response<ApiResult<T>> {
val response = delegate.execute()
return if (response.isSuccessful && response.body() != null ) {
Response.success(ApiResult.Success(response.body()!!))
} else {
Response.error(response.code(), response.errorBody()!!)
}
}

override fun isExecuted(): Boolean = delegate.isExecuted
override fun cancel() = delegate.cancel()
override fun isCanceled(): Boolean = delegate.isCanceled
override fun request(): Request = delegate.request()
override fun timeout(): Timeout = delegate.timeout()

}
Loading

0 comments on commit 1268d32

Please sign in to comment.