안드로이드/네트워크콜

Kotlin MVVM Coroutine을 활용해 Api call 하기

우선, API를 콜했을 때 Resource를 알기 위해 클래스를 생성합니다.

data class Resource<out T>(val status: Status, val data: T?, val message: ErrorMessage?) {
    companion object {
        fun <T> success(data: T?): Resource<T> {
            return Resource(Status.SUCCESS, data, null)
        }

        fun <T> error(msg: ErrorMessage, data: T?): Resource<T> {
            return Resource(Status.ERROR, data, msg)
        }

        fun <T> loading(data: T?): Resource<T> {
            return Resource(Status.LOADING, data, null)
        }
    }
}

 

각 응답은 networkCall을 호출하며 받을 것이며

MutableLiveData를 Coroutine을 통해 다룰 것이기 때문에

블럭을 suspend()로 바꿔주며 처리하는 펑션을 생성합니다.

fun <RESPONSE> networkCall(
    block: suspend () -> Response<RESPONSE>
): MutableLiveData<Resource<RESPONSE>> = CallHandler<RESPONSE>().makeCall(block)

이때 Response는 Retrofit2의 Response를 임포트해서 사용합니다.

networkCall이 실행될 때, Call을 컨트롤하는 CallHandler의 makeCall을 실행합니다.

 

 

네트워크 콜이 진행될 때, 콜을 컨트롤할 콜 핸들러 클래스를 생성합니다.

class CallHandler<RESPONSE> {
    private val job = Job()
    private val scope: CoroutineScope = CoroutineScope(job)

    fun makeCall(client: suspend () -> Response<RESPONSE>): MutableLiveData<Resource<RESPONSE>> {
        val result = MutableLiveData<Resource<RESPONSE>>()
        result.value = Resource.loading(null)
        scope.launch {
            try {
                val response = client()
                if (response.isSuccessful) {
                    withContext(Dispatchers.Main) {
                        result.value = Resource.success(response.body())
                    }
                } else {
                    // TODO
                    val errorMessage = ErrorMessage("40000", "Unknown error")
                    withContext(Dispatchers.Main) {
                        result.value = Resource.error(errorMessage, null)
                    }
                }
            } catch (e: Exception) {
                // TODO
                val errorMessage = ErrorMessage("50000", "Unknown error")
                withContext(Dispatchers.Main) {
                    result.value = Resource.error(errorMessage, null)
                }
            }
        }
        return result
    }
}

scope는 코루틴 스코프이며, 응답을 성공적으로 받았을 때, 성공적으로 받지 못했을 때를 처리할 수 있습니다.

각각 result라는 MutableLiveData의 value를 처리해줍니다.

withContext가 끝나기 전까지 코루틴은 일시정지되며, withContext의 마지막 줄이 반환됩니다.

비동기 작업을 순차화 하기 위해 쓰였습니다.

 

서울시 실시간 지하철 도착정보 API를 활용하여 예시를 만들어보자면,

interface ApiService {

    @GET("/api/subway/766e664c777067683838735346734d/json/realtimeStationArrival/0/10/서울")
    suspend fun seoulStationInfo(): Response<SubwayResponse>

}

ApiService 인터페이스를 만들어줍니다.

 

@Singleton
class SubwayRepository @Inject constructor(
    private val apiService: ApiService
) {

    fun seoulStationInfo(): MutableLiveData<Resource<SubwayResponse>> =
        networkCall {
            apiService.seoulStationInfo()
        }

}

networkCall 펑션을 사용하여 apiService의 seoulStationInfo()를 호출합니다.

networkCall 안에 있는 apiService.seoulStationInfo()는 suspend 블럭으로 실행이 됩니다.

 

MainActivity에서 해당 api를 호출하기 위해, observe를 활용합니다.

@Inject
lateinit var subwayRepository: SubwayRepository
subwayRepository.seoulStationInfo().observe(this@MainActivity) { result ->
        when (result.status) {
            Status.SUCCESS -> {
               	// 작업
                // 작업
                // 작업
            }
            Status.ERROR -> {
                result.data?.errorMessage?.let { Log.d(TAG, it.code) }
            }
            Status.LOADING -> {
            	// network Call이 실행되며 LOADING을 먼저 수행
            }
        }
    } // -- observe

 

이렇게 성공적으로 MVVM 패턴으로 코루틴을 이용해 API를 호출할 수 있습니다.