Resources for WearOS tiles and protolayout

Clue

·

Recently, I have been developing WearOS apps. A common element of these apps is tiles: like small screens that do one or more actions.

However, developing these screens is really a pain-in-the-ass. All the resources and things are hidden, which seems to be a common theme with Android documentation. Here I have gathered a few resources for creating tiles.

package org.fgclue.demos.tiles

import android.content.Context
import androidx.wear.protolayout.DeviceParametersBuilders
import androidx.wear.protolayout.DimensionBuilders.expand
import androidx.wear.protolayout.LayoutElementBuilders
import androidx.wear.protolayout.ResourceBuilders
import androidx.wear.protolayout.TimelineBuilders
import androidx.wear.protolayout.material3.MaterialScope
import androidx.wear.protolayout.material3.materialScope
import androidx.wear.protolayout.material3.primaryLayout
import androidx.wear.protolayout.material3.text
import androidx.wear.protolayout.modifiers.padding
import androidx.wear.protolayout.types.layoutString
import androidx.wear.tiles.RequestBuilders
import androidx.wear.tiles.TileBuilders
import androidx.wear.tiles.TileService
import androidx.wear.tiles.tooling.preview.Preview
import androidx.wear.tiles.tooling.preview.TilePreviewData
import androidx.wear.tiles.tooling.preview.TilePreviewHelper
import androidx.wear.tooling.preview.devices.WearDevices
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture

class GenericTileService : TileService() {
    @Suppress("PropertyName")
    val RESOURCES_VERSION = "type something here!"

    fun tileLayout(
        context: Context,
        deviceConfiguration: DeviceParametersBuilders.DeviceParameters
    ) =
        materialScope(
            context = context,
            deviceConfiguration = deviceConfiguration,
            allowDynamicTheme = false,
        ) {
            primaryLayout(
                titleSlot = {
                    text("My new tile".layoutString)
                },
                mainSlot = {
                    LayoutElementBuilders.Column.Builder()
                        .apply {
                            setWidth(expand())
                            setHeight(expand())
                            addContent(
                                text("Hello, World!".layoutString)
                            )
                        }
                        .build()
                },
            )
        }

    override fun onTileRequest(requestParams: RequestBuilders.TileRequest): ListenableFuture<TileBuilders.Tile> =
        Futures.immediateFuture(
            TileBuilders.Tile.Builder()
                .setResourcesVersion(RESOURCES_VERSION)
                .setTileTimeline(
                    TimelineBuilders.Timeline.fromLayoutElement(
                        tileLayout(this, requestParams.deviceConfiguration)
                    )
                )
                .build()
        )

    @Preview(device = WearDevices.SMALL_ROUND, name = "Small Round")
    @Preview(device = WearDevices.LARGE_ROUND, name = "Large Round")
    @Preview(device = WearDevices.SQUARE, name = "Square")
    internal fun tileLayoutPreview(context: Context): TilePreviewData {
        return TilePreviewData {
            TilePreviewHelper.singleTimelineEntryTileBuilder(
                tileLayout(context, it.deviceConfiguration)
            )
                .build()
        }
    }

    override fun onTileResourcesRequest(requestParams: RequestBuilders.ResourcesRequest): ListenableFuture<ResourceBuilders.Resources> =
        Futures.immediateFuture(
            ResourceBuilders.Resources.Builder().setVersion(RESOURCES_VERSION).build()
        )
}

Content licensed under CC0 1.0