Skip to content

Commit

Permalink
[#17] 통신을 위한 네트워크 관련 베이스 코드 작성
Browse files Browse the repository at this point in the history
- 리뷰 반영
- 레트로핏 버전 변수 정의
- Failure 시 Throwable로 다루도록 변경
- request url 기록
- Exception 구조화
  • Loading branch information
heechokim committed Apr 28, 2022
1 parent 76ba8a2 commit fc59d06
Show file tree
Hide file tree
Showing 11 changed files with 106 additions and 47 deletions.
5 changes: 3 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ android {

dependencies {
def glide_version = '4.13.1'
def retrofit_version = '2.9.0'

implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
Expand All @@ -91,8 +92,8 @@ dependencies {
annotationProcessor "com.github.bumptech.glide:compiler:$glide_version"

// Retrofit2
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"

// Coroutine
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0'
Expand Down
15 changes: 12 additions & 3 deletions app/src/main/java/com/moyerun/moyeorun_android/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ package com.moyerun.moyeorun_android
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.moyerun.moyeorun_android.common.Lg
import com.moyerun.moyeorun_android.network.callAdapter.ApiResult
import com.moyerun.moyeorun_android.common.exceptions.ApiException
import com.moyerun.moyeorun_android.network.calladapter.ApiResult
import com.moyerun.moyeorun_android.network.client.apiService
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
Expand All @@ -25,15 +26,23 @@ class MainActivity : AppCompatActivity() {
Lg.d("postman test Success response 2 ${response1.body.data}")
}
is ApiResult.Failure -> {
Lg.d("postman test Failure response 2 ${response1.errorBody.message}")
when (response1.exception) {
is ApiException -> {
Lg.d("postman test Failure response 2 ${response1.exception.message}")
}
}
}
}
when (response2) {
is ApiResult.Success -> {
Lg.d("postman test Success response 3 ${response2.body.data}")
}
is ApiResult.Failure -> {
Lg.d("postman test Failure response 3 ${response2.errorBody.message}")
when (response2.exception) {
is ApiException -> {
Lg.d("postman test Failure response 2 ${response2.exception.message}")
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.moyerun.moyeorun_android.common.exceptions

import com.moyerun.moyeorun_android.network.api.Error

class ApiException(val url: String, val error: Error) : RuntimeException() {
val case: String
get() = error.case

override val message: String?
get() = error.message
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.moyerun.moyeorun_android.common.exceptions

import java.lang.RuntimeException

class NetworkException(message: String, cause: Throwable) : RuntimeException(message, cause) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.moyerun.moyeorun_android.network.api

import com.moyerun.moyeorun_android.common.exceptions.ApiException

enum class ApiErrorCase(val case: String) {
// 서버와 합의된 에러 케이스들을 정의합니다.
NOT_LOGIN("100"), // TODO : 예시입니다. 실제 로그인 작업 시 수정해주세요.
UNKNOWN("999");

companion object {
private fun findError(case: String): ApiErrorCase {
return values().find { it.case == case } ?: UNKNOWN
}

fun getException(url: String, error: Error, cause: Throwable? = null): Throwable {
return when(findError(error.case)) {
NOT_LOGIN -> { TODO() }
else -> ApiException(url, error)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.moyerun.moyeorun_android.network.api

import com.moyerun.moyeorun_android.network.callAdapter.ApiResult
import com.moyerun.moyeorun_android.network.calladapter.ApiResult
import retrofit2.http.GET

interface ApiService {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package com.moyerun.moyeorun_android.network.callAdapter

import com.moyerun.moyeorun_android.network.api.Error
package com.moyerun.moyeorun_android.network.calladapter

/**
* Success : API 호출 성공 시, body를 Wrapping 합니다.
* Failure : API 호출 실패 시, errorBody를 Wrpping 합니다.
*/
sealed class ApiResult<out T> {
data class Success<T>(val body: T) : ApiResult<T>()
data class Failure(val errorBody: Error) : ApiResult<Nothing>()
data class Failure(val exception: Throwable) : ApiResult<Nothing>()
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
package com.moyerun.moyeorun_android.network.callAdapter
package com.moyerun.moyeorun_android.network.calladapter

import com.google.gson.Gson
import com.moyerun.moyeorun_android.common.exceptions.NetworkException
import com.moyerun.moyeorun_android.network.api.ApiErrorCase
import com.moyerun.moyeorun_android.network.api.Error
import com.moyerun.moyeorun_android.network.client.retrofit
import okhttp3.Request
import okhttp3.ResponseBody
import okio.Timeout
import retrofit2.*
import java.io.IOException

class ApiResultCall<S>(
private val delegate: Call<S>
) : Call<ApiResult<S>> {
override fun enqueue(callback: Callback<ApiResult<S>>) {
delegate.enqueue(object : Callback<S> {
override fun onResponse(call: Call<S>, response: Response<S>) {
val requestUrl = delegate.request().url().toString()

// status code 200번대.
if (response.isSuccessful) {
val body = response.body()
Expand All @@ -23,47 +25,62 @@ class ApiResultCall<S>(
Response.success(ApiResult.Success(body))
)
} else {
val message = "response body is null."
val case = "001" // TODO : 바디가 null일 경우 합의된 케이스 번호로 변경 필요함. 001은 임시 번호임.
callback.onResponse(
this@ApiResultCall,
Response.success(ApiResult.Failure(Error(message, case)))
Response.success(
ApiResult.Failure(
NetworkException(
"Response from $requestUrl was null " +
"but response body type was decleared as non-null",
HttpException(response)
)
)
)
)
}
} else { // status code 200 아닌 다른 것들.
val errorBody = response.errorBody()?.let { getErrorResponse(it) }
} else { // status code 200번대 아님.
val errorBody = response.errorBody()
if (errorBody != null) {
val errorResponse = Gson().fromJson(errorBody.string(), Error::class.java)
callback.onResponse(
this@ApiResultCall,
Response.success(ApiResult.Failure(errorBody))
Response.success(
ApiResult.Failure(
ApiErrorCase.getException(
requestUrl,
errorResponse,
HttpException(response)
)
)
)
)
} else {
val message = "error body is null."
val case = "002" // TODO : 에러 바디가 null일 경우 합의된 케이스 번호로 변경 필요함. 002은 임시 번호임.
callback.onResponse(
this@ApiResultCall,
Response.success(ApiResult.Failure(Error(message, case)))
Response.success(
ApiResult.Failure(
NetworkException(
requestUrl,
HttpException(response)
)
)
)
)
}
}
}

override fun onFailure(call: Call<S>, t: Throwable) {
val networkResponse = when (t) {
is IOException -> {
val message = "network error occurred."
val case = "003" // TODO : 네트워크 에러 발생 시 합의된 케이스 번호로 변경 필요함. 003은 임시 번호임.
Error(message, case)
}
else -> {
val message = "unknown error occurred."
val case = "004" // TODO : 언논 에러 발생 시 합의된 케이스 번호로 변경 필요함. 004는 임시 번호임.
Error(message, case)
}
}
override fun onFailure(call: Call<S>, throwable: Throwable) {
callback.onResponse(
this@ApiResultCall,
Response.success(ApiResult.Failure(networkResponse))
Response.success(
ApiResult.Failure(
NetworkException(
call.request().url().toString(),
throwable
)
)
)
)
}
})
Expand All @@ -72,7 +89,7 @@ class ApiResultCall<S>(
override fun clone(): Call<ApiResult<S>> = ApiResultCall(delegate.clone())

override fun execute(): Response<ApiResult<S>> {
throw UnsupportedOperationException("NetworkResponseCall doesn't support execute")
throw UnsupportedOperationException("ApiResultCall doesn't support execute")
}

override fun isExecuted(): Boolean = delegate.isExecuted
Expand All @@ -87,10 +104,4 @@ class ApiResultCall<S>(

override fun timeout(): Timeout = delegate.timeout()

private fun getErrorResponse(errorBody: ResponseBody): Error? {
return retrofit.responseBodyConverter<Error>(
Error::class.java,
Error::class.java.annotations
).convert(errorBody)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.moyerun.moyeorun_android.network.callAdapter
package com.moyerun.moyeorun_android.network.calladapter

import retrofit2.Call
import retrofit2.CallAdapter
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.moyerun.moyeorun_android.network.callAdapter
package com.moyerun.moyeorun_android.network.calladapter

import retrofit2.Call
import retrofit2.CallAdapter
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.moyerun.moyeorun_android.network.client

import com.moyerun.moyeorun_android.network.callAdapter.ApiResultCallAdapterFactory
import com.moyerun.moyeorun_android.network.calladapter.ApiResultCallAdapterFactory
import com.moyerun.moyeorun_android.network.api.ApiService
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
Expand Down

0 comments on commit fc59d06

Please sign in to comment.