먼저 Activity나 Fragment에는 Jetpack에서 이미 LifecycleScope을 이미 지원하고 있어서 별도로 중복해서 CoroutiineScope를 만드실 필요가 없구요. 그리고 lauch 빌더에 Dispatcher를 별도로 지정하지 않으시면 아마 Default가 사용되는 걸로 아는데, 님이 원하는 Network 호출은 Dispatchers.IO를 쓰시는 게 권장사항입니다.
그리고 Scope안에서 async를 통해 여러개 네트워크 호출을 하는 부분은 맞습니다.
인터넷이 연결이 안될 경우는 제 테스트 결과로는 Retrofit이 관련된 exception을 던지므로 try catch를 써서 해당 Exception을 잡아주셔야 합니다. Kotlin을 사용할 경우는 랭귀자 디자인상 Exception을 던지는 것보다는 잡아서 Exception임을 알 수 있는 클래스로 변환해서 리턴해주는 것이 일반적입니다. 이 부분은 자바 개발자들이 Kotlin에 대해 불평을 하는 이유 중의 하나이긴 한데, 언어자체가 그렇게 디자인이 되어있습니다.
따라서 네트워크 호출을 담당하는 클래스를 하나 만드셔서 여기서 exception를 잡으신 다음에 적절한 클래스로 변환해서 리턴해주도록 하는 것이 좋습니다. 흔히 사용하는 패턴을 예를 들면,
// 에러와 성공을 나타내는 sealed class. 에러일 때는 NetworkResult.Error, 성공일 때는
// NetworkResult.Success 를 리턴합니다.
sealed class NetworkResult<T> {
data class Error(val e: Exception): NetworkResult<Nothing>()
data class Success(val data: T): NetworkResult<Nothing>()
}
// 함수나 클래스를 만들어 retrofit service 함수 try catch 안에서 실행합니다.
// call 매개변수는 으로 T타입을 매개변수로 가지고 R 타입을 리턴하는 suspend function입니다.
// 필요하다면 Extension function으로 만들어서 사용하실 수 있을 것 같습니다.
fun tryApiCall(request: T, call: suspend (T) -> R): NetworkResult<R> {
try {
val result = call(request)
NetworkResult.Success(result);
} catch (e: IOException) {
NetworkResult.Error(e)
}
}
// 아래는 위의 함수를 이용하는 호출을 하는 코드입니다. 둘다 같은 코드입니다.
tryApiCall(request = 3, call = retService::getSortedAlbums)
tryApiCall(3) { albumId -> retService.getSortedAlbums(albumId) }
위와 같은 형태로 하실 수가 있을 겁니다. 키포인트는 Exception을 잡아서 적절한 형태의 Error와 Success를 표현할 수 있는 타입으로 리턴하는 겁니다. 그리고 일반적으로는 네트워크 호출은 data layer라고 부르는 별도의 패키지를 독립적으로 두고 거기에서 처리합니다. 뷰는 네트워크 호출이 어떻게 이루어지는지 알 필요가 없습니다.
네트워크 호출을 하시기 전에 인터넷 연결 상태를 체크해서 연결이 안된 상태는 호출을 하지 않고 에러메세지를 던지도록 추가할 수도 있겠죠.