-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #343 from Runnect/feature/test-apiresult-call-adpater
[Refactor] ViewModel에서 Flow를 사용할 수 있도록 수정
- Loading branch information
Showing
16 changed files
with
405 additions
and
33 deletions.
There are no files selected for viewing
18 changes: 18 additions & 0 deletions
18
app/src/main/java/com/runnect/runnect/data/dto/response/base/ErrorResponse.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.runnect.runnect.data.dto.response.base | ||
|
||
import kotlinx.serialization.SerialName | ||
import kotlinx.serialization.Serializable | ||
|
||
@Serializable | ||
data class ErrorResponse<T>( | ||
@SerialName("status") | ||
val status: Int, | ||
@SerialName("success") | ||
val success: Boolean?, | ||
@SerialName("message") | ||
val message: String?, | ||
@SerialName("error") | ||
val error: String?, | ||
@SerialName("data") | ||
val data: T? = null | ||
) |
80 changes: 80 additions & 0 deletions
80
app/src/main/java/com/runnect/runnect/data/network/calladapter/ResultCall.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package com.runnect.runnect.data.network.calladapter | ||
|
||
import com.google.gson.Gson | ||
import com.runnect.runnect.data.dto.response.base.ErrorResponse | ||
import com.runnect.runnect.domain.common.RunnectException | ||
import retrofit2.Call | ||
import retrofit2.Callback | ||
import retrofit2.Response | ||
import okhttp3.Request | ||
import okio.Timeout | ||
|
||
class ResultCall<T>(private val call: Call<T>) : Call<Result<T>> { | ||
|
||
private val gson = Gson() | ||
|
||
override fun execute(): Response<Result<T>> { | ||
throw UnsupportedOperationException("ResultCall doesn't support execute") | ||
} | ||
|
||
override fun enqueue(callback: Callback<Result<T>>) { | ||
call.enqueue(object : Callback<T> { | ||
override fun onResponse(call: Call<T>, response: Response<T>) { | ||
val apiResult = if (response.isSuccessful) { | ||
response.body()?.let { | ||
Result.success(it) | ||
} ?: Result.failure( | ||
RunnectException( | ||
code = response.code(), | ||
message = ERROR_MSG_RESPONSE_IS_NULL | ||
) | ||
) | ||
} else { | ||
Result.failure(parseErrorResponse(response)) | ||
} | ||
|
||
callback.onResponse( | ||
this@ResultCall, | ||
Response.success(apiResult) | ||
) | ||
} | ||
|
||
override fun onFailure(call: Call<T>, t: Throwable) { | ||
callback.onFailure(this@ResultCall, t) | ||
} | ||
}) | ||
} | ||
|
||
private fun parseErrorResponse(response: Response<*>): RunnectException { | ||
val errorJson = response.errorBody()?.string() | ||
|
||
return runCatching { | ||
val errorBody = gson.fromJson(errorJson, ErrorResponse::class.java) | ||
val message = errorBody?.run { | ||
message ?: error ?: ERROR_MSG_COMMON | ||
} | ||
|
||
RunnectException( | ||
code = errorBody.status, | ||
message = message | ||
) | ||
}.getOrElse { | ||
RunnectException( | ||
code = response.code(), | ||
message = ERROR_MSG_COMMON | ||
) | ||
} | ||
} | ||
|
||
override fun clone(): Call<Result<T>> = ResultCall(call.clone()) | ||
override fun isExecuted(): Boolean = call.isExecuted | ||
override fun cancel() = call.cancel() | ||
override fun isCanceled(): Boolean = call.isCanceled | ||
override fun request(): Request = call.request() | ||
override fun timeout(): Timeout = call.timeout() | ||
|
||
companion object { | ||
private const val ERROR_MSG_COMMON = "알 수 없는 에러가 발생하였습니다." | ||
private const val ERROR_MSG_RESPONSE_IS_NULL = "데이터를 불러올 수 없습니다." | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
app/src/main/java/com/runnect/runnect/data/network/calladapter/ResultCallAdapter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.runnect.runnect.data.network.calladapter | ||
|
||
import retrofit2.Call | ||
import retrofit2.CallAdapter | ||
import java.lang.reflect.Type | ||
|
||
class ResultCallAdapter<T>( | ||
private val responseType: Type | ||
) : CallAdapter<T, Call<Result<T>>> { | ||
|
||
override fun responseType() = responseType | ||
|
||
// Retrofit의 Call을 Result<>로 변환 | ||
override fun adapt(call: Call<T>): Call<Result<T>> { | ||
return ResultCall(call) | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
app/src/main/java/com/runnect/runnect/data/network/calladapter/ResultCallAdapterFactory.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package com.runnect.runnect.data.network.calladapter | ||
|
||
import retrofit2.Call | ||
import retrofit2.CallAdapter | ||
import retrofit2.Retrofit | ||
import java.lang.reflect.ParameterizedType | ||
import java.lang.reflect.Type | ||
|
||
class ResultCallAdapterFactory private constructor() : CallAdapter.Factory() { | ||
|
||
override fun get( | ||
returnType: Type, | ||
annotations: Array<Annotation>, | ||
retrofit: Retrofit | ||
): CallAdapter<*, *>? { | ||
// 최상위 타입이 Call인지 체크(suspend로 선언시 Call로 감싸짐) | ||
if (getRawType(returnType) != Call::class.java) { | ||
return null | ||
} | ||
|
||
check(returnType is ParameterizedType) { | ||
"Call return type must be parameterized as Call<Foo> or Call<out Foo>" | ||
} | ||
|
||
val responseType = getParameterUpperBound(0, returnType) | ||
if (getRawType(responseType) != Result::class.java) { | ||
return null | ||
} | ||
|
||
check(responseType is ParameterizedType) { | ||
"ApiResult return type must be parameterized as ApiResult<Foo> or ApiResult<out Foo>" | ||
} | ||
|
||
return ResultCallAdapter<Any>( | ||
getParameterUpperBound( | ||
0, | ||
responseType | ||
) | ||
) | ||
} | ||
|
||
companion object { | ||
@JvmStatic | ||
fun create() = ResultCallAdapterFactory() | ||
} | ||
} |
55 changes: 55 additions & 0 deletions
55
app/src/main/java/com/runnect/runnect/data/network/interceptor/ResponseInterceptor.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package com.runnect.runnect.data.network.interceptor | ||
|
||
import com.google.gson.Gson | ||
import com.google.gson.JsonParseException | ||
import com.google.gson.JsonSyntaxException | ||
import com.runnect.runnect.data.dto.response.base.BaseResponse | ||
import okhttp3.Interceptor | ||
import okhttp3.MediaType.Companion.toMediaTypeOrNull | ||
import okhttp3.Response | ||
import okhttp3.ResponseBody.Companion.toResponseBody | ||
import timber.log.Timber | ||
|
||
/** | ||
* BaseResponse에서 data만 추출 (불필요한 래핑 제거) | ||
* - 서버에서 내려준 형식이 아니라면 응답 그대로 반환 | ||
*/ | ||
class ResponseInterceptor : Interceptor { | ||
|
||
private val gson = Gson() | ||
|
||
override fun intercept(chain: Interceptor.Chain): Response { | ||
val originalResponse = chain.proceed(chain.request()) | ||
if (!originalResponse.isSuccessful) return originalResponse | ||
|
||
val bodyString = originalResponse.peekBody(Long.MAX_VALUE).string() | ||
val newResponseBodyString = jsonToBaseResponse(bodyString)?.let { | ||
it.toResponseBody("application/json".toMediaTypeOrNull()) | ||
} ?: return originalResponse | ||
|
||
return originalResponse.newBuilder() | ||
.code(originalResponse.code) | ||
.body(newResponseBodyString) | ||
.build() | ||
.apply { | ||
Timber.v("""\n | ||
origin = ${originalResponse.peekBody(Long.MAX_VALUE).string()} | ||
new = ${this.peekBody(Long.MAX_VALUE).string()} | ||
""".trimIndent() | ||
) | ||
} | ||
} | ||
|
||
private fun jsonToBaseResponse(body: String): String? { | ||
return try { | ||
val baseResponse = gson.fromJson(body, BaseResponse::class.java) | ||
gson.toJson(baseResponse.data) | ||
} catch (e: JsonSyntaxException) { | ||
null // JSON 구문 분석 오류 발생 시 원래 형식을 반환 | ||
} catch (e: JsonParseException) { | ||
null // JSON 파싱 오류 발생 시 원래 형식을 반환 | ||
} catch (e: Exception) { | ||
null // 기타 예외 발생 시 원래 형식을 반환 | ||
} | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
app/src/main/java/com/runnect/runnect/data/network/mapToFlowResult.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package com.runnect.runnect.data.network | ||
|
||
import com.runnect.runnect.domain.common.RunnectException | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.flow | ||
|
||
fun <R, D> Result<R>.mapToFlowResult( | ||
mapper: (R) -> D | ||
): Flow<Result<D>> = flow { | ||
val result = when { | ||
this@mapToFlowResult.isSuccess -> Result.success( | ||
// CallAdapter에서 body가 null인 경우도 걸러주고 있으므로 | ||
// Result.success의 데이터가 null인 경우는 없을듯함 | ||
mapper(this@mapToFlowResult.getOrNull()!!) | ||
) | ||
|
||
else -> Result.failure( | ||
this@mapToFlowResult.exceptionOrNull() ?: RunnectException() | ||
) | ||
} | ||
|
||
emit(result) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 9 additions & 0 deletions
9
app/src/main/java/com/runnect/runnect/data/service/CourseV2Service.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.runnect.runnect.data.service | ||
|
||
import com.runnect.runnect.data.dto.response.ResponseGetDiscoverMarathon | ||
import retrofit2.http.GET | ||
|
||
interface CourseV2Service { | ||
@GET("/api/public-course/marathon") | ||
suspend fun getMarathonCourse(): Result<ResponseGetDiscoverMarathon> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.