우선, 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를 호출할 수 있습니다.
'안드로이드 > 네트워크콜' 카테고리의 다른 글
Android Kotlin Coroutine을 통한 비동기 처리의 개념과 사용 예시 (0) | 2022.09.22 |
---|