1

I am doing something that I thought should be straightforward: A WearOS tile shall display the data fetched via https request. I took the goals tile example from the documentation and added a callback for fetching the data in my Tile Service:

private val repoRetriever = EnergyRetriever()
private val callback = object : Callback<EnergyResult> {
    override fun onFailure(call: Call<EnergyResult>?, t:Throwable?) {
        Log.e("MainActivity", "Problem calling Energy API {${t?.message}}")
    }

    override fun onResponse(call: Call<EnergyResult>?, response: Response<EnergyResult>?) {
        response?.isSuccessful.let {
            val energyResult = response?.body()
            Log.i("MainActivity", "SUCCESS! " + energyResult?.time + " - "+energyResult?.consumption + "kW")
            EnergyRepository.setEnergy(energyResult);
            getUpdater(getApplicationContext()).requestUpdate(GoalsTileService::class.java)
        }
    }
}

On the top of onTileRequest I initiate the request. I know that the tile will be rendered with the initial/old dataset, which is ok. I just want the tile to update once the data has been fetched:

override fun onTileRequest(requestParams: TileRequest) = serviceScope.future {
    if (isNetworkConnected()) {
        repoRetriever.getEnergyUpdate(callback)
    }
    // Retrieves data to populate the Tile.
    val energyResult = EnergyRepository.getEnergy()
    // Retrieves device parameters to later retrieve font styles for any text in the Tile.
    val deviceParams = requestParams.deviceParameters!!

    // Creates Tile.
    Tile.builder()
        // If there are any graphics/images defined in the Tile's layout, the system will
        // retrieve them via onResourcesRequest() and match them with this version number.
        .setResourcesVersion(RESOURCES_VERSION)
        .setFreshnessIntervalMillis(5 * 60 * 1000)
        // Creates a timeline to hold one or more tile entries for a specific time periods.
        .setTimeline(
            Timeline.builder().addTimelineEntry(
                TimelineEntry.builder().setLayout(
                    Layout.builder().setRoot(
                        // Creates the root [Box] [LayoutElement]
                        layout(energyResult!!, deviceParams)
                    )
                )
            )
        ).build()
}

This is obviously not working right because the onTileRequest will finish before the HTTP request is done. I also understand that one shouldn't block this function to wait. RequestUpdate() causes problems because the tile won't update again within a 20 second period due to limitations imposed by Google. I've read that one can use Futures in onTileRequest to defer the actual update until the http request returns - however I haven't been able to figure out just how and for the life of me I can't find an understandable example that would apply to what I'm trying to do. I don't even know if using callbacks for the http request is advisable when using futures here.

Anyone got suggestions?

Toumal
  • 562
  • 4
  • 10

1 Answers1

0

Starting this work from an incoming tile request is problematic for two reasons.

  1. The first tile request, likely stops you sending updates within the next twenty seconds. Ideally you should separate the data refresh from when you happen to get called for a tile. It also looks like your logic is effectively an infinite loop.
  2. Your app might be started temporarily for the tile request, and then return immediately, causing your tileservice to be unbound and your app to shutdown before your update completes.

Instead consider scheduling some background task with WorkManager, load your data, store is in Androidx DataStore or Room as a cache. Trigger the Tile update from Work Manager, and then load your data from the tile request out of that cache.

I don't have a clear example, but I do this in my app and it works well.

https://github.com/yschimke/rememberwear/blob/main/wear/src/main/kotlin/com/google/wear/soyted/app/work/DataRefreshWorker.kt

https://github.com/yschimke/rememberwear/blob/main/wear/src/main/kotlin/com/google/wear/soyted/tile/RememberWearTileProviderService.kt

Yuri Schimke
  • 12,435
  • 3
  • 35
  • 69
  • Hi there! You make some very good points, but wouldn't using workmanager cause these updates to be pulled regardless of tile visibility? Also, triggering a tile update from a background task doesn't work because the request typicall comes back faster than 20 seconds, and you are not allowed to perform an update before that time has elapsed. Basically I'm looking for a way to defer the tile update until the request is done, so I don't run the risk of running into update-jail. – Toumal Oct 14 '22 at 19:48
  • If you want a tile that fetches data almost interactively, then I think you are fighting the tile system. It's meant to allow users to glance at a tile, immediately see reasonably fresh data. Either by a reasonable refresh interval, a deliberate timeline of multiple tiles, or externally driven updates. A design that starts an async network operation when a tile request comes in, just seems problematic given the undefined service lifecycle. – Yuri Schimke Oct 15 '22 at 11:01
  • That would be okay if I was able to fetch data and display that data when the tile is shown. Having to wait 20 seconds for the next data update would be somewhat acceptable. However, I cannot seem to do that since the futures don't appear to work the way I'd expect, nor can I find any good example. I ended up abandoning the WearOS Platform entirely, and have implemented a much more powerful glance on Garmin Epix in 2 days. Weirdly enough on that platform I can do animations, data fetches etc and still have a week of battery . See https://github.com/dividebysandwich/SolarStatus-Garmin – Toumal Oct 18 '22 at 10:23
  • About the tile lifecycle: Updating in the background all the time even though nobody might look at the tile is, for me, the definition of wasting battery. I have seen several examples of this and I find it odd that nobody has any problems with it. The need to show *current* information is something I consider the default use case for something like tiles. Their very transitory nature makes these alleged battery saving restrictions all the more bewildering. Google's own tiles are exempt from such limitations. Anyway, I hope someone can still answer the original question. – Toumal Oct 18 '22 at 10:27