Update tiles

Create tiles with content that dynamically 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.

Diagram of tile 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)
        .setTimeline(Timeline.Builder()
            // We add a single timeline entry when our layout is fixed, and
            // we don't know in advance when its contents might change.
            .addTimelineEntry(TimelineEntry.Builder()
                // .setLayout(...).build()
            ).build()
        ).build()
    return Futures.immediateFuture(tile)
}

Java

@Override
protected ListenableFuture<Tile> onTileRequest(
       @NonNull TileRequest requestParams
) {
   Tile Tile = new Tile.Builder()
       .setResourcesVersion(RESOURCES_VERSION)
       .setTimeline(
           new Timeline.Builder()
               // We add a single timeline entry when our layout is fixed, and
               // we don't know in advance when its contents might change.
               .addTimelineEntry(new TimelineEntry.Builder()
                   // .setLayout(...).build()
               ).build()
       ).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 future events. Each future 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
    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
                TimeInterval.Builder()
                    .setEndMillis(meeting.dateTimeMillis).build()
            ).build()
        )
    }

    val tile = Tile.Builder()
        .setResourcesVersion(RESOURCES_VERSION)
        .setTimeline(timeline.build())
        .build()
    return Futures.immediateFuture(tile)
}

Java

@Override
protected ListenableFuture<Tile> onTileRequest(
       @NonNull TileRequest requestParams
) {
   Timeline.Builder timeline = new Timeline.Builder();
   // Add fallback "no meetings" entry
   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
                new TimeInterval.builder()
                    .setEndMillis(meeting.getDateTimeMillis()).build()
            ).build()
        );
    }

    Tile Tile = new Tile.Builder()
        .setResourcesVersion(RESOURCES_VERSION)
        .setTimeline(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, we might update its content once an 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
        .setTimeline(Timeline.Builder()
            .addTimelineEntry(TimelineEntry.Builder()
                .setLayout(getWeatherLayout())
                .build()
            ).build()
        ).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(new Timeline.Builder()
            .addTimelineEntry(new TimelineEntry.Builder()
                .setLayout(getWeatherLayout())
                .build()
            ).build()
        ).build());
}

When you set a freshness interval, the system calls onTileRequest() soon 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);
}