Android Developers Blog
The latest Android and Google Play news for app and game developers.
🔍
Platform Android Studio Google Play Jetpack Kotlin Docs News

26 August 2025

Ever-present and useful: Building complication data sources for Wear OS


Link copied to clipboard
Posted by Garan Jenkin – Developer Relations Engineer
This post is part of Wear OS Spotlight Week. Today, we're focusing on creating engaging experiences across the various surfaces available on the wrist.

Put your app's unique information directly on a user's watch face by building your own complications. These are the small, glanceable details on a watch face, like step count, date, or weather, that are used to convey additional information, beyond simply telling the time.

Watches such as the recently-launched Pixel Watch 4 feature watch faces with as many as 8 complications. These small, powerful display elements are a great way to provide quick, valuable information and keep users connected to your app.

Let’s look at how you can build your own complication data sources, surfacing useful information to the user directly on their watch face, and helping drive engagement with your app.

A round, analog Wear OS watch face showing 8 complications
A watch face showing 8 complications - 4 arc-style complications around the edge of the watch face, and 4 circular complications within the center of the watch face

Key principles of complications

In order to help understand complications, let’s first review some of the key architectural aspects of their design:

    • Apps provide only a complication data source - the watch face takes care of all layout and rendering.
    • Complication data is typed - both complication data sources and watch faces specify which types are supported respectively.
    • Watch faces define slots - these are spaces on the watch face that can host complications.
A flow chart illustrating the flow of requests and ComplicationData between the Wear OS system, watch face, and complication data source
The flow of requests and ComplicationData between the Wear OS system, watch face, and complication data source

What are complications good for?

Complications are great for providing the user with bite-size data during the course of the day. Additionally, complications can provide a great launch point into your full app experience.

Complications Data source types (full list) include SHORT_TEXT and SMALL_IMAGE. Similarly, watch faces declare what types they can render.

For example, if you’re building an app which includes fitness goals, a good choice for a complication data source might be one that provides the GOAL_PROGRESS or RANGED_VALUE data types, to show progress toward that goal.

Conversely, complications are less appropriate for larger amounts of data, such as the contents of a chat message. They’re also not suitable for very frequent updates, such as real-time fitness metrics generated by your app.

Creating a complication data source

Let’s look at creating a complication data source for that fitness goal mentioned above.

First, we create a service that extends SuspendingComplicationDataSourceService:

class MyDataSourceService : SuspendingComplicationDataSourceService() {
    override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationData? {
        // Handle both GOAL_PROGRESS and RANGED_VALUE
        return when (request.complicationType) {
            ComplicationType.GOAL_PROGRESS -> goalProgressComplicationData()
            ComplicationType.RANGED_VALUE -> rangedValueComplicationData()
            else -> NoDataComplicationData()
        }
    }

    // Apps should override this so that watch face previews contain
    // complication data
    override fun getPreviewData(type: ComplicationType) = createPreviewData()
}

To create the actual data to return, we create a ComplicationData object, shown here for GOAL_PROGRESS:

fun goalProgressComplicationData(): ComplicationData {
    val goalProgressText = PlainComplicationText
        .Builder("${goalProgressValue.toInt()} km")
        .build()
    return GoalProgressComplicationData.Builder(
        value = goalProgressValue,
        targetValue = goalTarget,
        contentDescription = goalProgressText
    )
    // Set some additional optional data
    .setText(goalProgressText)
    .setTapAction(tapAction)
    .setMonochromaticImage(...)
    .build()
}

Note: The GoalProgressComplicationData has numerous optional fields in addition to the mandatory ones. You should try to populate as many of these as you can.

Finally, add the data source to the manifest:

<service
    android:name=".WorkoutStatusDataSourceService"
    android:exported="true"
    android:directBootAware="true"
    android:label="@string/status_complication_label"
    android:permission="com.google.android.wearable.permission.BIND_COMPLICATION_PROVIDER">
    <intent-filter>
        <action android:name="android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST" />
    </intent-filter>

    <!--
      Supported data types. Note that the preference order of the watch face,
      not the complication data source, decides which type will be chosen.
    -->
    <meta-data
        android:name="android.support.wearable.complications.SUPPORTED_TYPES"
        android:value="GOAL_PROGRESS,RANGED_VALUE" />
    <meta-data
        android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
        android:value="300" />
</service>
Note: The use of the directBootAware attribute on the service lets the complication service run before the user has unlocked the device on boot.

Choosing your update model

Complications support both a push and a pull-style update mechanism. In the example above, UPDATE_PERIOD_SECONDS is set such that the data is refreshed every 5 minutes. Wear OS will check the updated value of the complication data source with that frequency.

This works well for a scenario such as a weather complication, but in other scenarios, it may make more sense for the updates to be driven by the app. To achieve this, you can:

  1. Set UPDATE_PERIOD_SECONDS to 0 to indicate that the app will drive the update process.
  2. Using ComplicationDataSourceUpdateRequester in your app code to signal to the Wear OS system that an update should be requested, for example in a WorkManager job, or in WearableListenerService.

Leveraging platform bindings for high-frequency data

Particularly for health-related complications, we can take advantage of platform data sources, to improve our goal progress complication. We can use these data sources with dynamic expressions to create complication content which is dynamically re-evaluated every second while the watch face is in interactive mode (that is, when it’s not in system ambient / always-on mode).

Let’s update the complication so that instead of just showing the distance, it shows a celebratory message when the target is reached. First we create a dynamic string as follows:

val distanceKm = PlatformHealthSources.dailyDistanceMeters().div(1000f)
val formatter = DynamicBuilders.DynamicFloat.FloatFormatter.Builder()
    .setMaxFractionDigits(2)
    .setMinFractionDigits(0)
    .build()
val goalProgressText = DynamicBuilders.DynamicString
    .onCondition(distanceKm.lt(distanceKmTarget))
    .use(
        distanceKm
            .format(formatter)
            .concat(DynamicBuilders.DynamicString.constant(" km"))
    )
    .elseUse(
        DynamicBuilders.DynamicString.constant("Success!")
    )

Then we include this text, and the dynamic value distanceKm, with the dynamic version of the complication builder.

In this way, the distance is updated every second, with no need for further requests to the data source. This means UPDATE_PERIOD_SECONDS can be set to a large value, saving battery, and the celebratory text is immediately shown the moment they pass their target!

Configuring complications

For some data sources, it is useful to let the user configure what data should be shown. In the fitness goal example, consider that the user might have weekly, monthly, and yearly goals.

Adding a configuration activity allows them to select which goal should be shown by the complication. To do this, add the PROVIDER_CONFIG_ACTION metadata to your service definition, and implement an activity with a filter for this intent, for example:

<service android:name=".MyGoalDataSourceService" ...>
  <!-- ... -->

  <meta-data
      android:name="android.support.wearable.complications.PROVIDER_CONFIG_ACTION"
      android:value="com.myapp.MY_GOAL_CONFIG" />
</service>

<activity android:name=".MyGoalConfigurationActivity" ...>
  <intent-filter>
    <action android:name="com.myapp.MY_GOAL_CONFIG" />
    <category android:name="android.support.wearable.complications.category.PROVIDER_CONFIG" />
    <category android:name="android.intent.category.DEFAULT" />
  </intent-filter>
</activity>

In the activity itself, the details of the complication being configured can be extracted from the intent:

// Keys defined on ComplicationDataSourceService
// (-1 assigned when the ID or type was not available)
val id = intent.getIntExtra(EXTRA_CONFIG_COMPLICATION_ID, -1)
val type = intent.getIntExtra(EXTRA_CONFIG_COMPLICATION_TYPE, -1)
val source = intent.getStringExtra(EXTRA_CONFIG_DATA_SOURCE_COMPONENT)

To indicate a successful configuration, the activity should set the result when exiting:

setResult(Activity.RESULT_OK) // Or RESULT_CANCELED to cancel configuration
finish()

The ID is the same ID passed in ComplicationRequest to the complication data source service. The Activity should write any configuration to a data store, using the ID as a key, and the service can retrieve the appropriate configuration to determine what data to return in response to each onComplicationRequest().

Working efficiently with time and events

In the example above, UPDATE_PERIOD_SECONDS is set at 5 minutes - this is the smallest value that can be set for the update period. Ideally this value should be set as large as is acceptable for the use case: This reduces requests and improves battery life.

Consider these examples:

    • A known list of events - For example a calendar. In this case, use SuspendingTimelineComplicationDataSourceService.
    • This allows you to provide the series of events in advance, with no need for the watch face to request updates. The calendar data source would only need to push updates if a change is made, such as another event being scheduled for the day, offering timeliness and efficiency.

      ComplicationDataTimeline requires a defaultComplicationData as well as the list of entries: This is used in the case where none of the timeline entries are valid for the current time. For example, for a calendar it could contain the text “No event” where the user has nothing booked. Where there are overlapping entries, the entry with the shortest interval is chosen.

override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationDataTimeline? {
    return ComplicationDataTimeline(
        // The default for when there is no event in the calendar
        defaultComplicationData = noEventComplicationData,
        // A list of calendar entries
        timelineEntries = listOf(
            TimelineEntry(
                validity = TimeInterval(event1.start, event1.end),
                complicationData = event1.complicationData
            ),
            TimelineEntry(
                validity = TimeInterval(event2.start, event2.end),
                complicationData = event2.complicationData
            )
        )
    )
}

    • Working with time or timers - If your complication data contains time or a timer, such as a countdown to a particular event, use built-in classes such as TimeDifferenceComplicationText and TimeFormatComplicationText - this keeps the data up-to-date while avoiding regular requests to the data source service.
    • For example, to create a countdown to the New Year:

TimeDifferenceComplicationText.Builder(
    TimeDifferenceStyle.SHORT_SINGLE_UNIT,
    CountDownTimeReference(newYearInstant)
)
.setDisplayAsNow(true)
.build()

    • Data that should be shown at a specific time and/or duration - use setValidTimeRange() to control when complication data should be shown, again avoiding repeated updates.
    • This can be useful in the case where it is not possible to use a timeline but where data can become stale, allowing you to control the visibility of this data.

Working with activation and deactivation

It can be very useful to track whether your complication is currently in use on the active watch face or not. This can help with:

  1. Avoiding unnecessary work - for example, if a weather complication has not been set in the active watch face, then there is no need to enable a WorkManager job to periodically fetch weather updates, saving battery and network usage.
  2. Aiding discovery - if onComplicationActivated has never been called, then the user has never used your complication on a watch face.
  3. This can be a useful signal to provide an educational moment in your phone or Wear OS app, drawing attention to this feature, and sharing potential benefits with the user that they may not be aware of.

To facilitate these use cases, override the appropriate methods in your complication service:

class MyDataSourceService() : SuspendingComplicationDataSourceService() {
    override fun onComplicationActivated(complicationInstanceId: Int, type: ComplicationType) {
        super.onComplicationActivated(complicationInstanceId, type)

        // Keep track of which complication has been enabled, and
        // start any necessary work such as registering periodic
        // WorkManager jobs
    }
    
    override fun onComplicationDeactivated(complicationInstanceId: Int) {
        super.onComplicationDeactivated(complicationInstanceId)

        // Complication instance has been disabled, so remove all
        // registered work
    }

Some additional points to consider when implementing your data sources:

    • Support multiple types to maximize usefulness and compatibility - Watch faces will support some complication data types, but likely not all of them.
    • Adding support to your data source for multiple types makes it most useful to the user. In the above example, we implemented both RANGED_VALUE and GOAL_PROGRESS, as both can be used to represent progress-type data.

      Similarly, if you were to implement a calendar complication, you could use both SHORT_TEXT and LONG_TEXT to maximize compatibility with the available slots on the watch face.

    • Use different data sources for different user journeys - Your app is not limited to providing one complication data source. You should support more than one if you have different use cases to cater for. For example, your health and fitness app might have a complication to provide your progress towards your goals, but also a separate complication to show sleep stats.
    • Avoid heavy work in onComplicationRequest() - For example,if the progress toward a fitness goal involves intensive processing of a large number of workouts, do this elsewhere. The request to the complication data source should ideally just return the value with minimal computation.
    • Avoid your service having extensive dependencies on other app components - When in use, your data source service will be started when the Wear OS device starts up, and at other times during the day. You should avoid the service needing too many other components from within your app to be started in order to run, to maintain good system performance.
    • Consider backup and restore - If the complication is configurable, it might make sense to restore these settings - learn how to implement backup and restore for complication data sources.
    • Think about the discovery journey - Your complications will be available as an option on the user’s watch face when your app is installed on the watch. Consider how you can promote and educate the user on this functionality, both in your phone app and your Wear OS app, and leverage methods such as onComplicationActivated() to inform this process.
    • Resources for creating complications

      Complications are a great way to elevate your app experience for users, and to differentiate your app from others.

      Check out these resources for more information on creating complication data sources. We look forward to seeing what you can do.

      Happy Coding!