Create tiles with content that changes as time passes.
Work with timelines
A timeline consists of one or more
TimelineEntry
instances, each of which contain a layout that is displayed during a specific
time interval. All tiles need a timeline.
Single-entry tiles
Often a tile can be described with a single
TimelineEntry
. The layout is
fixed, and only the information inside the layout changes. For example, a tile
that shows your fitness progress of the day always shows the same progress
layout, though you might adjust that layout to show different values. In these
cases, you don't know in advance when the content might change.
See the following example of a tile with a single
TimelineEntry
:
Kotlin
override fun onTileRequest(
requestParams: TileRequest
): ListenableFuture<Tile> {
val tile = Tile.Builder()
.setResourcesVersion(RESOURCES_VERSION)
// We add a single timeline entry when our layout is fixed, and
// we don't know in advance when its contents might change.
.setTileTimeline(
Timeline.fromLayoutElement(...)
).build()
return Futures.immediateFuture(tile)
}
Java
@Override
protected ListenableFuture<Tile> onTileRequest(
@NonNull TileRequest requestParams
) {
Tile tile = new Tile.Builder()
.setResourcesVersion(RESOURCES_VERSION)
// We add a single timeline entry when our layout is fixed, and
// we don't know in advance when its contents might change.
.setTileTimeline(
Timeline.fromLayoutElement(...)
).build();
return Futures.immediateFuture(tile);
}
Timebound timeline entries
A
TimelineEntry
can optionally define a validity period, allowing a tile to
change its layout at a known time without requiring the app to push a new tile.
The canonical example is an agenda tile whose timeline contains a list of
upcoming events. Each upcoming event contains a validity period to indicate when
to show it.
The tiles API allows for overlapping validity periods, where the screen with the
shortest period of time left is the one shown. Only one event is displayed at a
time.
Developers can provide a default fallback entry. For example, the agenda tile
could have a tile with an infinite validity period, which is used if no other
timeline entry is valid, as shown in the following code sample:
Kotlin
public override fun onTileRequest(
requestParams: TileRequest
): ListenableFuture<Tile> {
val timeline = Timeline.Builder()
// Add fallback "no meetings" entry
// Use the version of TimelineEntry that's in androidx.wear.protolayout.
timeline.addTimelineEntry(TimelineEntry.Builder()
.setLayout(getNoMeetingsLayout())
.build()
)
// Retrieve a list of scheduled meetings
val meetings = MeetingsRepo.getMeetings()
// Add a timeline entry for each meeting
meetings.forEach { meeting ->
timeline.addTimelineEntry(TimelineEntry.Builder()
.setLayout(getMeetingLayout(meeting))
.setValidity(
// The tile should disappear when the meeting begins
// Use the version of TimeInterval that's in
// androidx.wear.protolayout.
TimeInterval.Builder()
.setEndMillis(meeting.dateTimeMillis).build()
).build()
)
}
val tile = Tile.Builder()
.setResourcesVersion(RESOURCES_VERSION)
.setTileTimeline(timeline.build())
.build()
return Futures.immediateFuture(tile)
}
Java
@Override
protected ListenableFuture<Tile> onTileRequest(
@NonNull RequestBuilders.TileRequest requestParams
) {
Timeline.Builder timeline = new Timeline.Builder();
// Add fallback "no meetings" entry
// Use the version of TimelineEntry that's in androidx.wear.protolayout.
timeline.addTimelineEntry(new TimelineEntry.Builder().setLayout(getNoMeetingsLayout()).build());
// Retrieve a list of scheduled meetings
List<Meeting> meetings = MeetingsRepo.getMeetings();
// Add a timeline entry for each meeting
for(Meeting meeting : meetings) {
timeline.addTimelineEntry(new TimelineEntry.Builder()
.setLayout(getMeetingLayout(meeting))
.setValidity(
// The tile should disappear when the meeting begins
// Use the version of TimeInterval that's in
// androidx.wear.protolayout.
new TimeInterval.builder()
.setEndMillis(meeting.getDateTimeMillis()).build()
).build()
);
}
Tile tile = new Tile.Builder()
.setResourcesVersion(RESOURCES_VERSION)
.setTileTimeline(timeline.build())
.build();
return Futures.immediateFuture(tile);
}
Refresh a tile
Information shown on a tile might expire after some time. For example, a weather
tile that shows the same temperature throughout the day isn't accurate.
To deal with expiring data, set a freshness interval at the time of creating a
tile, which specifies how long the tile is valid. In the example of the weather
tile, you might update its content every hour, as shown in the following code
sample:
Kotlin
override fun onTileRequest(requestParams: RequestBuilders.TileRequest) =
Futures.immediateFuture(Tile.Builder()
.setResourcesVersion(RESOURCES_VERSION)
.setFreshnessIntervalMillis(60 * 60 * 1000) // 60 minutes
.setTileTimeline(Timeline.fromLayoutElement(
getWeatherLayout())
).build()
)
Java
@Override
protected ListenableFuture<Tile> onTileRequest(
@NonNull TileRequest requestParams
) {
return Futures.immediateFuture(new Tile.Builder()
.setResourcesVersion(RESOURCES_VERSION)
.setFreshnessIntervalMillis(60 * 60 * 1000) // 60 minutes
.setTimeline(Timeline.fromLayoutElement(
getWeatherLayout())
).build());
}
When you set a freshness interval, the system calls
onTileRequest()
shortly after the interval finishes. If you don't set a freshness interval, the
system doesn't call
onTileRequest()
.
A tile can also expire because of an external event. For example, a user might
remove a meeting from their calendar, and if the tile wasn't refreshed, then the
tile would still show that deleted meeting. In this case, request a refresh from
any place in your application code, as shown in the following code sample:
Kotlin
fun eventDeletedCallback() {
TileService.getUpdater(context)
.requestUpdate(MyTileService::class.java)
}
Java
public void eventDeletedCallback() {
TileService.getUpdater(context)
.requestUpdate(MyTileService.class);
}
Choose an update workflow
Use these best practices to determine how to configure your tile updates:
- If the update is predictable?for example, if it's for the next event in
the user's calendar?use a timeline.
- When you fetch platform data, use data binding so that the system updates
the data automatically.
If the update can be calculated on-device in a small amount of time?such
as updating the position of an image on a sunrise tile?use
onTileRequest()
.
This is particularly useful when you need to generate all images ahead of
time. If you need to generate a new image at a future time, call
setFreshnessIntervalMillis()
.
If you're doing more intensive background work repeatedly, such as polling
for weather data, use
WorkManager
, and push updates to your tile.
If the update is in response to an external event?such as the lights
turning on, receiving an email, or updating a note?send a
Firebase Cloud
Messaging (FCM)
message to make your app active again, then push updates
to the tile.
If the tile data sync process might be expensive, do the following:
- Schedule a data sync.
- Start a timer for 1-2 seconds.
- If you receive an update from a remote data source before time runs out,
show the updated value from the data sync. Otherwise, show a cached
local value.
Recommended for you