Skip to content

Commit

Permalink
[#17] 통신을 위한 네트워크 관련 베이스 코드 작성
Browse files Browse the repository at this point in the history
- 클래스, 파일 이름 변경
  • Loading branch information
heechokim committed May 10, 2022
1 parent edd691c commit 64d1da8
Show file tree
Hide file tree
Showing 8 changed files with 250 additions and 0 deletions.
38 changes: 38 additions & 0 deletions app/src/main/java/com/moyerun/moyeorun_android/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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") }
}
}
}
}
14 changes: 14 additions & 0 deletions app/src/main/java/com/moyerun/moyeorun_android/network/Response.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.moyerun.moyeorun_android.network

/**
* 서버 팀과 합의한 성공/실패 응답 값에 대한 모델입니다.
*/
data class Success<T>(
val message: String,
val data: T
)

data class Failure<T>(
val message: String,
val error: T
)
Original file line number Diff line number Diff line change
@@ -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<Success<Any>, Failure<Any>>

@GET("appName")
suspend fun getAppName(): NetworkResult<Success<Any>, Failure<Any>>
}
Original file line number Diff line number Diff line change
@@ -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<out Annotation>,
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<Result<Foo>> or Call<Result<out Foo>>"
}

// 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<Foo> or Result<out Foo>"
}

val successBodyType = getParameterUpperBound(0, responseType)
val errorBodyType = getParameterUpperBound(1, responseType)

val errorBodyConverter = retrofit.nextResponseBodyConverter<Any>(null, errorBodyType, annotations)

return NetworkResponseCallAdapter<Any, Any>(successBodyType, errorBodyConverter)
}
}
Original file line number Diff line number Diff line change
@@ -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<S : Any, E : Any>(
private val delegate: Call<S>,
private val errorConverter: Converter<ResponseBody, E>
) : Call<NetworkResult<S, E>> {

override fun enqueue(callback: Callback<NetworkResult<S, E>>) {
return delegate.enqueue(object : Callback<S> {
override fun onResponse(call: Call<S>, response: Response<S>) {
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<S>, 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<NetworkResult<S, E>> = NetworkResponseCall(delegate.clone(), errorConverter)

override fun execute(): Response<NetworkResult<S, E>> {
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()
}
Original file line number Diff line number Diff line change
@@ -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<S : Any, E : Any>(
private val successType: Type,
private val errorBodyConverter: Converter<ResponseBody, E>
) : CallAdapter<S, Call<NetworkResult<S, E>>> {
override fun responseType(): Type = successType

override fun adapt(call: Call<S>): Call<NetworkResult<S, E>> = NetworkResponseCall(call, errorBodyConverter)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.moyerun.moyeorun_android.network.callAdapter

import java.io.IOException

sealed class NetworkResult<out T : Any, out U : Any> {
data class Success<T : Any>(val body: T) : NetworkResult<T, Nothing>()
data class Failure<U : Any>(val body: U, val code: Int) : NetworkResult<Nothing, U>()
data class NetworkError(val error: IOException) : NetworkResult<Nothing, Nothing>()
data class UnknownError(val error: Throwable?) : NetworkResult<Nothing, Nothing>()
}
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 64d1da8

Please sign in to comment.