This part of the series was the most difficult for me. The concepts behind complications are incredibly tied to the Android ecosystem, and specifically the Wear OS framework. I have done my best at all times to separate the rendering elements from the watch specific components.

A Watch Face complication, as mentioned in Part 2, is a horological term that refers to a part of a watch face that is not concerned with hours, minutes and seconds. These elements are more complex in the information age, because they could be anything. This last article is not concerned with the data that is provided to the complication, but rather the element that uses existing complication data providers to render information on the watch face.

The ComplicationDataSource.kt class associates the data that should be rendered with the element that is drawn on the screen. Data to be rendered is collected by the ComplicatedWatchFaceService.kt and updates the rendering element. However the positioning of the elements on the screen are handled separately by the implementation of the ComplicationsRenderer, the example can be found in ExampleWatchComplicationRenderer.kt:

class ExampleWatchComplicationRenderer(context: Context) : WatchComplicationsRenderer(context) {
    companion object {
        const val LEFT_COMPLICATION_ID = 0
        const val RIGHT_COMPLICATION_ID = 1

        val complicationsList = intArrayOf(
            LEFT_COMPLICATION_ID,
            RIGHT_COMPLICATION_ID
        )
    }

    override val complicationIdList: IntArray
        get() = complicationsList

    override fun updateStyle() {
        dataSource.updateStyle(screenSettings)
    }

    override fun surfaceChanged(width: Int, height: Int) {
        val sizeOfComplication = width / 4
        val midpointOfScreen = width / 2

        val horizontalOffset = (midpointOfScreen - sizeOfComplication) / 2
        val verticalOffset = midpointOfScreen - sizeOfComplication / 2

        val leftBounds =  // Left, Top, Right, Bottom
            Rect(
                horizontalOffset,
                verticalOffset,
                horizontalOffset + sizeOfComplication,
                verticalOffset + sizeOfComplication
            )

        val rightBounds =  // Left, Top, Right, Bottom
            Rect(
                midpointOfScreen + horizontalOffset,
                verticalOffset,
                midpointOfScreen + horizontalOffset + sizeOfComplication,
                verticalOffset + sizeOfComplication
            )

        dataSource.complicationDrawableList.apply {
            get(LEFT_COMPLICATION_ID)
                .bounds = leftBounds
            get(RIGHT_COMPLICATION_ID)
                .bounds = rightBounds
        }
    }
}

The advantage of separation of rendering from specific data proves vital for the harness so that a dummy data source (DrawableComplicationDataSource.kt) can be provided in order to render the complications on the WatchFaceView.kt. Once again, this abstraction in the Wear OS Watch Face itself means that the ComplicatedWatchFaceService.kt only concerns itself with the specific lifecycle attributed with complications.

I strongly recommend going through Google’s CodeLab on Wear OS watch face complications and start making watch faces. It’s also advisable to go through the activity code to see the manner in which a user can select which complication gets rendered on the screen.