Android Jetpack tile throwing java.util.concurrent.CancellationException

Viewed 60

I am developing an Android Wear Tile and occasionally my app crashes with this stack trace.

2022-06-22 15:19:24.753 24101-24101/myapp E/AndroidRuntime: FATAL EXCEPTION: main
    Process: myapp, PID: 24101
    java.util.concurrent.CancellationException
        at kotlinx.coroutines.guava.JobListenableFuture.getInternal(ListenableFuture.kt:438)
        at kotlinx.coroutines.guava.JobListenableFuture.get(ListenableFuture.kt:428)
        at androidx.wear.tiles.TileService$TileProviderWrapper.lambda$onTileRequest$0(TileService.java:234)
        at androidx.wear.tiles.TileService$TileProviderWrapper$$ExternalSyntheticLambda7.run(Unknown Source:4)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:246)
        at android.app.ActivityThread.main(ActivityThread.java:7690)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:593)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:995)
     Caused by: kotlinx.coroutines.JobCancellationException: Job was cancelled; job=JobImpl{Cancelled}@d298571

I think this is occurring when my tile is refreshing. My tile is getting data from a REST API using Retrofit. The code looks something like this:

class WeatherTileService : TileService() {
    private val serviceScope = CoroutineScope(Dispatchers.IO)

    override fun onTileRequest(requestParams: TileRequest) = serviceScope.future {
        Log.d("Tile", "Refreshing weather data")
        val api = AmbientWeatherClient.getInstance()
        val devices = api.listUsersDevices(APP_KEY, API_KEY)
...
        Tile.Builder()
            .setResourcesVersion(RESOURCES_VERSION)
            .setFreshnessIntervalMillis(5 * 60 * 1000)
            .setTimeline(

interface AmbientWeatherAPI {

    @GET("devices")
    suspend fun listUsersDevices(
        @Query("applicationKey") applicationKey: String,
        @Query("apiKey") apiKey: String
    ): List<Device>

}

I cannot figure out why this is happening. Any suggestions where to start digging?

1 Answers

I am surprised the cancellation kills your app. But you have a window of 10 seconds to complete that request. I suspect this is the cancellation.

/**
 * Called when the system is requesting a new timeline from this Tile Service. The returned
 * future must complete after at most 10 seconds from the moment this method is called (exact
 * timeout length subject to change).
 *
 * <p>Note that this is called from your app's main thread, which is usually also the UI thread.
 *
 * @param requestParams Parameters about the request. See {@link TileRequest} for more info.
 */
@MainThread
@NonNull
protected abstract ListenableFuture<Tile> onTileRequest(@NonNull TileRequest requestParams);

My advice would be never do any variable length work in onTileRequest, such as calling a remote API. It generally makes more sense to be serving this from a cache, such as a Room database.

Also the network may not be available when the tile request comes in. For this and many other reasons, it's typically better to have some background process updating data, such as WorkManager. This can then send an update to your Tile, but also any complications or the App itself.

Related