Andrey Stepankov
04/02/2020, 1:57 PMclass MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
private val singleThread = this + newSingleThreadContext("SingleThread")
override fun onCreate(savedInstanceState: Bundle?) {
Timber.plant(Timber.DebugTree())
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val api = PokemonApi.createApi()
button.setOnClickListener {
singleThread.launch {
try {
val names = async { api.getPokemonNames() }
val types = async { api.getPokemonTypes() }
val stages = async { api.getPokemonStages() }
val owners = async { api.getPokemonOwners() }
names.join()
types.join()
stages.join()
owners.join()
} catch (e: UnknownHostException) {
// exception is not consumed
}
}
}
val crashHandler = Thread.getDefaultUncaughtExceptionHandler()
val exceptionHandler = Thread.UncaughtExceptionHandler { thread, exception ->
try {
Timber.tag("UncaughtException").e(exception)
} finally {
crashHandler?.uncaughtException(thread, exception)
}
}
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler)
}
interface PokemonApi {
@GET("/names")
suspend fun getPokemonNames()
@GET("/types")
suspend fun getPokemonTypes()
@GET("/stages")
suspend fun getPokemonStages()
@GET("/owners")
suspend fun getPokemonOwners()
companion object {
fun createApi(): PokemonApi {
return Retrofit.Builder()
.baseUrl("<https://unavailable-host.com>")
.build()
.create(PokemonApi::class.java)
}
}
}
}
stacktrace
E/UncaughtException: <http://java.net|java.net>.UnknownHostException: Unable to resolve host "<http://unavailable-host.com|unavailable-host.com>": No address associated with hostname
at <http://java.net|java.net>.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:156)
at <http://java.net|java.net>.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:103)
at <http://java.net|java.net>.InetAddress.getAllByName(InetAddress.java:1152)
at okhttp3.Dns$Companion$DnsSystem.lookup(Dns.kt:49)
at okhttp3.internal.connection.RouteSelector.resetNextInetSocketAddress(RouteSelector.kt:164)
at okhttp3.internal.connection.RouteSelector.nextProxy(RouteSelector.kt:129)
at okhttp3.internal.connection.RouteSelector.next(RouteSelector.kt:71)
at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.kt:199)
at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.kt:108)
at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.kt:76)
at okhttp3.internal.connection.RealCall.initExchange$okhttp(RealCall.kt:245)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:32)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:100)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:82)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:100)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:83)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:100)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:74)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:100)
at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:197)
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:502)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:919)
Suppressed: <http://java.net|java.net>.UnknownHostException: Unable to resolve host "<http://unavailable-host.com|unavailable-host.com>": No address associated with hostname
... 24 more
Caused by: android.system.GaiException: android_getaddrinfo failed: EAI_NODATA (No address associated with hostname)
at libcore.io.Linux.android_getaddrinfo(Native Method)
at libcore.io.ForwardingOs.android_getaddrinfo(ForwardingOs.java:74)
at libcore.io.BlockGuardOs.android_getaddrinfo(BlockGuardOs.java:200)
at libcore.io.ForwardingOs.android_getaddrinfo(ForwardingOs.java:74)
at <http://java.net|java.net>.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:135)
... 23 more
Caused by: android.system.GaiException: android_getaddrinfo failed: EAI_NODATA (No address associated with hostname)
at libcore.io.Linux.android_getaddrinfo(Native Method)
at libcore.io.ForwardingOs.android_getaddrinfo(ForwardingOs.java:74)
at libcore.io.BlockGuardOs.android_getaddrinfo(BlockGuardOs.java:200)
at libcore.io.ForwardingOs.android_getaddrinfo(ForwardingOs.java:74)
at <http://java.net|java.net>.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:135)
... 23 more
E/AndroidRuntime: FATAL EXCEPTION: SingleThread
Process: com.example.myapplication, PID: 24936
streetsofboston
04/02/2020, 2:02 PMAndrey Stepankov
04/02/2020, 2:03 PMlouiscad
04/02/2020, 2:03 PMasync
in a local coroutineScope
, and put your try
block around it. Without it, the launch
gets the crash, cancels all children coroutines and crashes its parent scope.streetsofboston
04/02/2020, 2:05 PMawait()
on async calls’ Deferred<T>
results be sufficient?Andrey Stepankov
04/02/2020, 2:05 PMlouiscad
04/02/2020, 2:10 PMCancellationException
) will cancel its parent scope. So if you want to handle errors without crashing the parent scope, you need a middle scope (coroutineScope { ... }
), and catch exceptions/throwables around it. The intent of the behavior is to cancel fast work in the same scope if one fails, to avoid pursuing unneeded work that cannot be used as a part already failed.streetsofboston
04/02/2020, 2:12 PMSupervisorJob
MainActivity’s CoroutineScope in the should be sufficient to not cancel the MainActivity’s CoroutineScope after a failure happens.
In my opinion, if names
fails, you want the others (types
, stages
, etc) to be cancelled as well.Andrey Stepankov
04/02/2020, 2:13 PMlaunch {
try {
getCats()
} catch() {
// no-op
}
}
// getCats will throw UnknownHostException
suspend fun callApi() = httpClient.getCats()
louiscad
04/02/2020, 2:15 PMlifecycleScope
extension brought by AndroidX Lifecycle runtime KTX 2.2.0+ that already uses a SupervisorJob
for the case where you use independant async
where one can fail while letting the other still be useful on its own.Andrey Stepankov
04/02/2020, 2:17 PMMainScope() + newSingleThreadContext("SingleThread")
streetsofboston
04/02/2020, 2:17 PMval names = async { api.getPokemonNames() }
val types = async { api.getPokemonTypes() }
val stages = async { api.getPokemonStages() }
val owners = async { api.getPokemonOwners() }
val listOfResults = listOf(names, types, stages, owners).awaitAll()
... or call `await()` on each one individually depending on your use-case...
lifecycleScope
instead. It is all set up correctly already (lifecycleScope.launch { …. }
) 🙂Andrey Stepankov
04/02/2020, 2:28 PM