From 64d1da84585c9102d111b379da521f66dcd6ab3d Mon Sep 17 00:00:00 2001 From: Kimchohee Date: Fri, 1 Apr 2022 23:06:14 +0900 Subject: [PATCH] =?UTF-8?q?[#17]=20=ED=86=B5=EC=8B=A0=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EB=84=A4=ED=8A=B8=EC=9B=8C=ED=81=AC=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EB=B2=A0=EC=9D=B4=EC=8A=A4=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20-=20=ED=81=B4=EB=9E=98=EC=8A=A4,=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moyerun/moyeorun_android/MainActivity.kt | 38 ++++++++ .../moyeorun_android/network/Response.kt | 14 +++ .../network/api/ApiService.kt | 15 +++ .../NetworkResponseAdapterFactory.kt | 47 ++++++++++ .../callAdapter/NetworkResponseCall.kt | 94 +++++++++++++++++++ .../callAdapter/NetworkResponseCallAdapter.kt | 16 ++++ .../network/callAdapter/NetworkResult.kt | 10 ++ .../network/client/Retrofit.kt | 16 ++++ 8 files changed, 250 insertions(+) create mode 100644 app/src/main/java/com/moyerun/moyeorun_android/network/Response.kt create mode 100644 app/src/main/java/com/moyerun/moyeorun_android/network/api/ApiService.kt create mode 100644 app/src/main/java/com/moyerun/moyeorun_android/network/callAdapter/NetworkResponseAdapterFactory.kt create mode 100644 app/src/main/java/com/moyerun/moyeorun_android/network/callAdapter/NetworkResponseCall.kt create mode 100644 app/src/main/java/com/moyerun/moyeorun_android/network/callAdapter/NetworkResponseCallAdapter.kt create mode 100644 app/src/main/java/com/moyerun/moyeorun_android/network/callAdapter/NetworkResult.kt create mode 100644 app/src/main/java/com/moyerun/moyeorun_android/network/client/Retrofit.kt diff --git a/app/src/main/java/com/moyerun/moyeorun_android/MainActivity.kt b/app/src/main/java/com/moyerun/moyeorun_android/MainActivity.kt index 85a3746..3ec6d54 100644 --- a/app/src/main/java/com/moyerun/moyeorun_android/MainActivity.kt +++ b/app/src/main/java/com/moyerun/moyeorun_android/MainActivity.kt @@ -2,10 +2,48 @@ 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.NetworkResult +import com.moyerun.moyeorun_android.network.client.apiService +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + + apiCallTest() + } + + private fun apiCallTest() { + GlobalScope.launch { + val response1 = apiService.getUserRepoList("choheeis") + when (response1) { + is NetworkResult.Success -> { + Lg.d("getUserRepoList Success") } + is NetworkResult.Failure -> { + Lg.d("getUserRepoList ApiError") + } + is NetworkResult.NetworkError -> { + // 네트워크 에러와 언논 에러는 전처리되면 좋을 듯 + // Success랑 API Error만 뷰 모델에서 관리하도록. + Lg.d("getUserRepoList NetworkError") + } + is NetworkResult.UnknownError -> { Lg.d("getUserRepoList UnknownError") } + } + + val response2 = apiService.getAppName() + when (response2) { + is NetworkResult.Success -> { Lg.d("getAppName Success") } + is NetworkResult.Failure -> { + // 비즈니스 로직 상의 에러 + // 이넘 클래스로 별도 관리하는 에러 코드와 비교하여 알맞은 로직 작성 + Lg.d("getAppName ApiError") + } + is NetworkResult.NetworkError -> { Lg.d("getAppName NetworkError") } + is NetworkResult.UnknownError -> { Lg.d("getAppName UnknownError") } + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/moyerun/moyeorun_android/network/Response.kt b/app/src/main/java/com/moyerun/moyeorun_android/network/Response.kt new file mode 100644 index 0000000..1f0633d --- /dev/null +++ b/app/src/main/java/com/moyerun/moyeorun_android/network/Response.kt @@ -0,0 +1,14 @@ +package com.moyerun.moyeorun_android.network + +/** + * 서버 팀과 합의한 성공/실패 응답 값에 대한 모델입니다. + */ +data class Success( + val message: String, + val data: T +) + +data class Failure( + val message: String, + val error: T +) \ No newline at end of file diff --git a/app/src/main/java/com/moyerun/moyeorun_android/network/api/ApiService.kt b/app/src/main/java/com/moyerun/moyeorun_android/network/api/ApiService.kt new file mode 100644 index 0000000..2a88d3e --- /dev/null +++ b/app/src/main/java/com/moyerun/moyeorun_android/network/api/ApiService.kt @@ -0,0 +1,15 @@ +package com.moyerun.moyeorun_android.network.api + +import com.moyerun.moyeorun_android.network.Failure +import com.moyerun.moyeorun_android.network.Success +import com.moyerun.moyeorun_android.network.callAdapter.NetworkResult +import retrofit2.http.GET +import retrofit2.http.Path + +interface ApiService { + @GET("users/{user}/repos") + suspend fun getUserRepoList(@Path("user") user: String): NetworkResult, Failure> + + @GET("appName") + suspend fun getAppName(): NetworkResult, Failure> +} \ No newline at end of file diff --git a/app/src/main/java/com/moyerun/moyeorun_android/network/callAdapter/NetworkResponseAdapterFactory.kt b/app/src/main/java/com/moyerun/moyeorun_android/network/callAdapter/NetworkResponseAdapterFactory.kt new file mode 100644 index 0000000..6317229 --- /dev/null +++ b/app/src/main/java/com/moyerun/moyeorun_android/network/callAdapter/NetworkResponseAdapterFactory.kt @@ -0,0 +1,47 @@ +package com.moyerun.moyeorun_android.network.callAdapter + +import retrofit2.Call +import retrofit2.CallAdapter +import retrofit2.Retrofit +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type + +class NetworkResponseAdapterFactory : CallAdapter.Factory() { + + override fun get( + returnType: Type, + annotations: Array, + retrofit: Retrofit + ): CallAdapter<*, *>? { + + // suspend functions wrap the response type in `Call` + if (Call::class.java != getRawType(returnType)) { + return null + } + + // check first that the return type is `ParameterizedType` + check(returnType is ParameterizedType) { + "return type must be parameterized as Call> or Call>" + } + + // get the response type inside the `Call` type + val responseType = getParameterUpperBound(0, returnType) + + // if the response type is not ApiResponse then we can't handle this type, so we return null + if (getRawType(responseType) != NetworkResult::class.java) { + return null + } + + // the response type is ApiResponse and should be parameterized + check(responseType is ParameterizedType) { + "Response must be parameterized as Result or Result" + } + + val successBodyType = getParameterUpperBound(0, responseType) + val errorBodyType = getParameterUpperBound(1, responseType) + + val errorBodyConverter = retrofit.nextResponseBodyConverter(null, errorBodyType, annotations) + + return NetworkResponseCallAdapter(successBodyType, errorBodyConverter) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/moyerun/moyeorun_android/network/callAdapter/NetworkResponseCall.kt b/app/src/main/java/com/moyerun/moyeorun_android/network/callAdapter/NetworkResponseCall.kt new file mode 100644 index 0000000..905831b --- /dev/null +++ b/app/src/main/java/com/moyerun/moyeorun_android/network/callAdapter/NetworkResponseCall.kt @@ -0,0 +1,94 @@ +package com.moyerun.moyeorun_android.network.callAdapter + +import okhttp3.Request +import okhttp3.ResponseBody +import okio.Timeout +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Converter +import retrofit2.Response +import java.io.IOException +import java.lang.Exception +import java.lang.UnsupportedOperationException + +internal class NetworkResponseCall( + private val delegate: Call, + private val errorConverter: Converter +) : Call> { + + override fun enqueue(callback: Callback>) { + return delegate.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + val body = response.body() + val code = response.code() + val error = response.errorBody() + + if (response.isSuccessful) { + // status code가 200번대 일 때 + if (body != null) { + callback.onResponse( + this@NetworkResponseCall, + Response.success(NetworkResult.Success(body)) + ) + } else { + // body 없을 때 별도 에러 만들어도 될 듯. + callback.onResponse( + this@NetworkResponseCall, + Response.success(NetworkResult.UnknownError(null)) + ) + } + } else { + val errorBody = when { + error == null -> null + error.contentLength() == 0L -> null + else -> try { + errorConverter.convert(error) + } catch (e: Exception) { + null + } + } + + if (errorBody != null) { + callback.onResponse( + this@NetworkResponseCall, + Response.success(NetworkResult.Failure(errorBody, code)) + ) + } else { + callback.onResponse( + this@NetworkResponseCall, + Response.success(NetworkResult.UnknownError(null)) + ) + } + } + } + + override fun onFailure(call: Call, t: Throwable) { + val networkResponse = when (t) { + is IOException -> NetworkResult.NetworkError(t) + else -> NetworkResult.UnknownError(t) + } + // 여기서 전처리 하면 됨. + callback.onResponse( + this@NetworkResponseCall, + Response.success(networkResponse) + ) + } + }) + } + + override fun clone(): Call> = NetworkResponseCall(delegate.clone(), errorConverter) + + override fun execute(): Response> { + throw UnsupportedOperationException("NetworkResponseCall doesn't support execute") + } + + 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() +} \ No newline at end of file diff --git a/app/src/main/java/com/moyerun/moyeorun_android/network/callAdapter/NetworkResponseCallAdapter.kt b/app/src/main/java/com/moyerun/moyeorun_android/network/callAdapter/NetworkResponseCallAdapter.kt new file mode 100644 index 0000000..47721e8 --- /dev/null +++ b/app/src/main/java/com/moyerun/moyeorun_android/network/callAdapter/NetworkResponseCallAdapter.kt @@ -0,0 +1,16 @@ +package com.moyerun.moyeorun_android.network.callAdapter + +import okhttp3.ResponseBody +import retrofit2.Call +import retrofit2.CallAdapter +import retrofit2.Converter +import java.lang.reflect.Type + +class NetworkResponseCallAdapter( + private val successType: Type, + private val errorBodyConverter: Converter +) : CallAdapter>> { + override fun responseType(): Type = successType + + override fun adapt(call: Call): Call> = NetworkResponseCall(call, errorBodyConverter) +} \ No newline at end of file diff --git a/app/src/main/java/com/moyerun/moyeorun_android/network/callAdapter/NetworkResult.kt b/app/src/main/java/com/moyerun/moyeorun_android/network/callAdapter/NetworkResult.kt new file mode 100644 index 0000000..1545e3c --- /dev/null +++ b/app/src/main/java/com/moyerun/moyeorun_android/network/callAdapter/NetworkResult.kt @@ -0,0 +1,10 @@ +package com.moyerun.moyeorun_android.network.callAdapter + +import java.io.IOException + +sealed class NetworkResult { + data class Success(val body: T) : NetworkResult() + data class Failure(val body: U, val code: Int) : NetworkResult() + data class NetworkError(val error: IOException) : NetworkResult() + data class UnknownError(val error: Throwable?) : NetworkResult() +} \ No newline at end of file diff --git a/app/src/main/java/com/moyerun/moyeorun_android/network/client/Retrofit.kt b/app/src/main/java/com/moyerun/moyeorun_android/network/client/Retrofit.kt new file mode 100644 index 0000000..624beb2 --- /dev/null +++ b/app/src/main/java/com/moyerun/moyeorun_android/network/client/Retrofit.kt @@ -0,0 +1,16 @@ +package com.moyerun.moyeorun_android.network.client + +import com.moyerun.moyeorun_android.network.callAdapter.NetworkResponseAdapterFactory +import com.moyerun.moyeorun_android.network.api.ApiService +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + +private const val BASE_URL = "https://api.github.com/" + +private val retrofit: Retrofit = Retrofit.Builder() + .baseUrl(BASE_URL) + .addCallAdapterFactory(NetworkResponseAdapterFactory()) + .addConverterFactory(GsonConverterFactory.create()) + .build() + +val apiService: ApiService = retrofit.create(ApiService::class.java) \ No newline at end of file