Retrofit response.errorBody.string() gives me warning of inappropriate blocking method call in suspend function

Viewed 1797

I am developing an Android App.

I am using Retrofit2 and Coroutine to fetch some data from my Rest API.

When there is an exception thrown in my Rest API, it returns exception code and exception message with HTTP status = 4xx or 5xx as per the screenshot below

enter image description here

If the response is an exception, then I map the exceptionCode and exceptionMessage to the response in my Android app as per the code below.

val exceptionBody = Gson().fromJson(response.errorBody()?.string(), ExceptionResponse::class.java)

This is ExceptionResponse class

data class ExceptionResponse(
    val exceptionCode: String,
    val exceptionMessage: String
)

Here is a problem. When I do response.errorBody()?.string(), Android Studio gives me a warning saying "inappropriate blocking method call"

Here is my repository calling the network call

override fun fetchData() {
    CoroutineScope(Dispatchers.IO).launch {
        val fetchedData = myRemoteDataSource.fetchData()
    }
}

Here is MyRemoteDataSource class

class MyRemoteDataSourceImpl(
    private val myAPIService: MyAPIService
): BaseRemoteDataSource(), MyRemoteDataSource {

    override suspend fun fetchData(): Resource<List<Data>> {
        return getResult {
            myAPIService.fetchData()
        }
    }
}

And here is my BaseRemoteDataSource class which has the getResult() where errorBody.string is called

enter image description here

As you can see in the screenshot above, the only coroutine that does not give me the warning is the last one

GlobalScope.launch(Dispatchers.IO) {
    Gson().fromJson(response.errorBody()?.string(), ExceptionResponse::class.java)
}

So I have a few questions about this warning and coroutineScope

  1. Why the last one does not give me the warning but every other coroutine scopes?
GlobalScope.launch(Dispatchers.IO) {
    Gson().fromJson(response.errorBody()?.string(), ExceptionResponse::class.java)
}

  1. It seems like I need structured concurrency because I need to parse JSON and return it. If I CoroutineScope or GlobalScope then I will return null unless I use scope.join. Then shouldn't I use coroutineScope() which is using the parent/caller's coroutine scope for structured concurrency?

  1. It looks like response.errorBody()?.string() is parsing JSON which is supposed to be in Dispatchers.Default, is not it? Just for your reference, I include the source code for string()
  public final String string() throws IOException {
    try (BufferedSource source = source()) {
      Charset charset = Util.bomAwareCharset(source, charset());
      return source.readString(charset);
    }
  }

  1. Is it fine to use the last one because then I am creating GlobalScope in CoroutineScope.

  1. does withContext() use caller's coroutine scope just like coroutineScope() or does it create a new scope with different Dispatchers?

Sorry for dumping questios at once but they are all related. Thank you Guys!!!

1 Answers

It is normal to call blocking executions in the IO context because it is designed to do such tasks. So the best way, I think, is to write an extension function on the ResponseBody to do it in the IO context appropriately. However, on this function, the warning also appears, but as we know that we handled it well, we can suppress it.

@Suppress("BlockingMethodInNonBlockingContext")
suspend fun ResponseBody.stringSuspending() =
    withContext(Dispatchers.IO) { string() }

So we are sure that ResponseBody.string() will be executed in IO context, no matter it might be wrapped with another context:

Gson().fromJson(response.errorBody()?.stringSuspending(), ExceptionResponse::class.java)
Related