Skip to content
Advertisement

How to send data to Android Wear TileService

I don’t really know, how to feed my Wear OS tiles with data. Currently, I am experimenting with receiving my heart rate from a BroadcastReceiver, which works but my value is always 0 inside onTileRequest.

This is my TileService…

public class HeartRateTileService extends TileService {
    private static final String RESOURCES_VERSION = "1";

    private String currentHeartrate = "0";

    @Override          //onStartCommand never gets called
    public int onStartCommand(Intent intent, int flags, int startId) {
        int i = super.onStartCommand(intent, flags, startId);
        Log.d("HeartRate", "onStartCommand " + currentHeartrate);
        LocalBroadcastManager.getInstance(this).registerReceiver(messageReceiver, new IntentFilter("heart-rate-to-activity"));
        return i;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        LocalBroadcastManager.getInstance(this).registerReceiver(messageReceiver, new IntentFilter("heart-rate-to-activity"));
    }

    @NonNull
    @Override
    protected ListenableFuture<TileBuilders.Tile> onTileRequest(
            @NonNull RequestBuilders.TileRequest requestParams
    ) {
        Log.d("HeartRate", "onTileRequest " + currentHeartrate);
        return Futures.immediateFuture(new TileBuilders.Tile.Builder()
                .setResourcesVersion(RESOURCES_VERSION)
                .setFreshnessIntervalMillis(1000*60)
                .setTimeline(new TimelineBuilders.Timeline.Builder()
                        .addTimelineEntry(new TimelineBuilders.TimelineEntry.Builder()
                                .setLayout(new LayoutElementBuilders.Layout.Builder()
                                        .setRoot(
                                                layout()
                                        ).build()
                                ).build()
                        ).build()
                ).build()
        );
    }

    private LayoutElementBuilders.LayoutElement layout() {
        return new LayoutElementBuilders.Row.Builder()
                .setWidth(wrap())
                .setHeight(expand())
                .setVerticalAlignment(LayoutElementBuilders.VERTICAL_ALIGN_CENTER)
                .addContent(new LayoutElementBuilders.Text.Builder()
                        .setText(currentHeartrate)
                        .build()
                ).build();
    }

    @NonNull
    @Override
    protected ListenableFuture<ResourceBuilders.Resources> onResourcesRequest(
            @NonNull RequestBuilders.ResourcesRequest requestParams
    ) {
        Log.d("HeartRate", "onResourcesRequest " + currentHeartrate);
        return Futures.immediateFuture(new ResourceBuilders.Resources.Builder()
                .setVersion(RESOURCES_VERSION)
                .build()
        );
    }

    private final BroadcastReceiver messageReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d("HeartRate", "message: " + currentHeartrate);
            currentHeartrate = intent.getExtras().getString("message");
        }
    };

}

This is what I get… As you see onTileRequest is always 0. Maybe it’s because onCreate gets called too whenever onTileRequest gets called and therefore my data is 0 at this moment. But how can I save the heart rate permanently then? Am I doing something wrong? Or are tiles just not meant to receive data of a BroadcastReceiver. Are there other ways? I literally found nothing about updating tiles with data on the internet.

I/OpenGLRenderer: Initialized EGL, version 1.4
D/OpenGLRenderer: Swap behavior 2
D/HeartRate: onCreate 0
W/t.myapplicatio: Accessing hidden field Ljava/nio/Buffer;->address:J (light greylist, reflection)
D/HeartRate: onTileRequest 0
D/HeartRate: onResourcesRequest 0
D/ViewRootImpl[MainActivity]: changeCanvasOpacity: opaque=false
D/HeartRate: onCreate 0
D/HeartRate: message: 0
D/HeartRate: message: 0
D/HeartRate: message: 81
D/HeartRate: message: 81
D/HeartRate: onCreate 0
D/HeartRate: message: 81
    message: 81
    message: 0
D/HeartRate: message: 81
I/chatty: uid=10096(com.pezcraft.myapplication) identical 1 line
D/HeartRate: message: 82
D/HeartRate: message: 72
I/chatty: uid=10096(com.pezcraft.myapplication) identical 1 line
D/HeartRate: message: 72
D/HeartRate: message: 73
I/chatty: uid=10096(com.pezcraft.myapplication) identical 1 line
D/HeartRate: message: 73
D/HeartRate: onCreate 0
D/HeartRate: onTileRequest 0
D/HeartRate: message: 75
I/chatty: uid=10096(com.pezcraft.myapplication) identical 1 line
D/HeartRate: message: 75
    message: 0
D/HeartRate: message: 75
I/chatty: uid=10096(com.pezcraft.myapplication) identical 2 lines
D/HeartRate: message: 75

Advertisement

Answer

The lifecycle of the TileService isn’t the same as your application. Your tile service might be destroyed and a new one created for additional requests. It’s probably unlikely that the service created to produce a tile, was awake when the heartbeat sensor data was received.

This thread on Android Widgets https://twitter.com/marxallski/status/1454889465206513667 also applies to Wear Tiles and Complications.

Ideally data is cached in memory, if not, use a worker to refresh it and whenever data is cached it should notify the widget to update.

You should not keep data inside the GlanceAppWidget, it should be kept at the application scope

The same applies here.

Your tile should execute very quickly, effectively statelessly

  • not initiating a potentially long or failing network request, or subscribing to background data
  • returning data cached in memory (application scope) or read from a DB (Room?)

Try subscribing to the data outside the tile. Store the data tied to the application (a field in your Application subclass), use DI like Koin/Hilt, or store in a database that you can read out of when you get the onTileRequest request.

I’m not very familiar with Health data or Broadcasts – but this seems relevant https://developer.android.com/training/wearables/health-services/passive

Advertisement