3
votes

I need to fetch some data from a REST API, everything is ok when I'm 4G or wifi connected but when I'm in airplane mode, the app crashes with : "E/AndroidRuntime: FATAL EXCEPTION: main"

Before that I have a log (not an error saying : "Skipped 1013 frames! The application may be doing too much work on its main thread.")

So I suppose fetching the API with no network crashes the app because it's running in the main thread. BUT I'm using coroutines and to me, I'm doing it right :

ViewModel

private val viewModelJob = SupervisorJob()
private val viewModelScope = CoroutineScope(viewModelJob + Dispatchers.Main)
init {
        viewModelScope.launch {
            videosRepository.refreshVideos()
        }
    }

Repository

suspend fun refreshVideos() {
        withContext(Dispatchers.IO) {
            val playlist = Network.devbytes.getPlaylist().await()
            //database.videoDao().insertAll(*playlist.asDatabaseModel())
        }
    }

Service

/**
 * A retrofit service to fetch a devbyte playlist.
 */
interface DevbyteService {
    @GET("devbytes.json")
    fun getPlaylist(): Deferred<NetworkVideoContainer>
}

/**
 * Build the Moshi object that Retrofit will be using, making sure to add the Kotlin adapter for
 * full Kotlin compatibility.
 */
private val moshi = Moshi.Builder()
        .add(KotlinJsonAdapterFactory())
        .build()

/**
 * Main entry point for network access. Call like `Network.devbytes.getPlaylist()`
 */
object Network {
    // Configure retrofit to parse JSON and use coroutines
    private val retrofit = Retrofit.Builder()
            .baseUrl("https://devbytes.udacity.com/")
            .addConverterFactory(MoshiConverterFactory.create(moshi))
            .addCallAdapterFactory(CoroutineCallAdapterFactory())
            .build()

    val devbytes: DevbyteService = retrofit.create(DevbyteService::class.java)
}

So the complete chain is :

ViewModel -> coroutine with Dispatchers.Main

that calls Repository -> suspend function that launch a coroutine with Dispatchers.IO

that calls Service -> through object Network, I get a retrofit instance with a getPlaylist() that returns a Deferred, and the call to that method is in the repository with an await()

What am I doing wrong ?

2

2 Answers

4
votes

Your API call is throwing an exception because there is no network connection (most likely UnknownHostException).

Wrap it in a try-catch and deal with the exception.

3
votes

CoroutineExceptionHandler might be a solution. https://kotlinlang.org/docs/reference/coroutines/exception-handling.html#coroutineexceptionhandler

When you turn on the airplane mode, the coroutine that do network calls will throw an exception. In your case, you can do something like this.

    val handler = CoroutineExceptionHandler { _, exception -> 
            //Handle your exception
        }
    init {
        viewModelScope.launch(handler) {
            videosRepository.refreshVideos()
        }
    }