This codelab will teach you how to build a complication data provider.

Concepts and setup

At the end of the codelab, you will understand how to enable your data to appear in complications on Android Wear.

Concepts

To start off, let's learn a little bit about Complications. A complication is a feature of a watch face beyond hours and minutes. For example, the date or a steps indicator is a complication (see image below).

The Complications API is for both watch faces and data provider apps. Let's look at the players:

In this code lab, we cover creating a complication data provider. If you are also interested in adding complications to your watch face, check out our other code lab, "Adding Complications to your Watch Face," after you are finished.

Let's get started!

Clone the starter project repo

To get started as quickly as possible, we have prepared a starter project for you to build on. It contains some basic code and application settings necessary for the code lab.

If you have Git installed, you can simply run the command below. (You can check if Git is installed by typing git --version in the terminal / command line and verify it executes correctly.)

 git clone https://github.com/googlecodelabs/data-providers.git

If you do not have Git, you can download the project as a zip file:

Download Zip

Import the project

Start Android Studio, and select "Open an existing Android Studio project" from the Welcome screen, open the project directory, and double-click on the build.gradle file in the ComplicationsDataProvider directory:

Click OK on "Import Project from Gradle" screen without making any changes. (You may see a screenshot like the one below.)

After the project has loaded, you may also see an alert like the one below, you can click "Ignore" or the "X" in the upper right. (You won't be pushing any changes back to the Git repo.)

In the upper-left corner of the project window, you should see something like the screenshot below if you are in the Android view. (If you are in the Project view, you will need to expand the ComplicationsDataProvider project to see the same thing.)

There are five folder icons. Each is a "module". Please note that Android Studio might take several seconds to compile the project in the background for the first time. During this time you will see a spinner in the status bar at the bottom of Android Studio:

We recommend that you wait until this has finished before making code changes. This will allow Android Studio to pull in all the necessary components. In addition, if you get a prompt saying "Reload for language changes to take effect?" or something similar, select "Yes".

Understand the starter project

All right, you're set up and ready to start exposing your data to complications. We'll set off using the 1-base module, which is the starting point for everything we'll be building upon. You will be adding code from each step to 1-base.

Each of the following modules can be used as reference points to check your work or for reference if you encounter any issues. The number in front of the module name corresponds with the codelab step.

Overview of key components

Emulator setup

If you need help setting up an Android Wear emulator, please refer to the "Set Up an Android Wear Emulator or Device" section of the "Creating and Running a Wearable App" article.

Run the starter project

Let's run it on a watch.

Waiting for device.
Target device: lge-urbane_2-XXXXXXXXXXXXXX
Uploading file
        local path: ~/Downloads/ComplicationsDataProvider /1-base/build/outputs/apk/1-base-debug.apk
        remote path: /data/local/tmp/com.example.android.wearable.complicationsdataprovider
Installing com.android.example.watchface
DEVICE SHELL COMMAND: pm install -r "/data/local/tmp/com.example.android.wearable.complicationsdataprovider"
pkg: /data/local/tmp/com.example.android.wearable.complicationsdataprovider
Success

Please note, since this is the first time you are running this watch face, you will need to select it from the favorites menu. After it has been selected once, it will show up as one of the options alongside this option.

Here's what it should look like. Don't worry if your emulator has a cloud with a strikethrough in place of the airplane icon. We will not need a connection to a phone / internet for this code lab.

Summary

In this step you've learned about:

Next up

Let's start exposing some data.

Code step 2

In this step, we will start exposing data. Complications accept several types of data. In this step, we will return the Short Text data type.

If at any point you are confused by the concepts discussed here, please refer to the "2-short-data" module and see how these steps may be implemented.

Specifying the data types your provider supports

Open the AndroidManifest.xml and look at the service RandomNumberProviderService.

Notice the intent-filter:

<action android:name=
    "android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST"/>

This tells the system that your service extends ComplicationProviderService and can send data for complications.

Next is the meta-data element specifying the data type(s) you support. In this case, we support SMALL_IMAGE, but for this step, you will need to change that to SHORT_TEXT. Change your first meta-data element to read this:

<meta-data
    android:name="android.support.wearable.complications.SUPPORTED_TYPES"
    android:value="SHORT_TEXT"/>

Specifying how often the complication should refresh your data

Next is the meta-data element UPDATE_PERIOD_SECONDS. This specifies how often you want the system to check for updates to the data when your data provider is active. In this case, we will want to change the update frequency from 0 to 120.

Note: When UPDATE_PERIOD_SECONDS is set to zero, you are using a "push style" to send updates, rather than requesting updates on a fixed schedule. This code lab focuses on the fixed schedule. Check out the links at the end if you want to learn more about the "push style".

<meta-data 
    android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
    android:value="120"/>

The time of 120 seconds is relatively short so we can observe a result in this code lab. In everyday use, developers should consider intervals in the order of minutes. Also, remember that this is only a guidance value for the system. Android Wear may do updates less frequently.

Exposing your data

As stated earlier, onComplicationActivated() is called when your data provider is activated, but onComplicationUpdate()is where the active complication requests updated data.

The method onComplicationUpdate() can be triggered for various reasons:

Open RandomNumberProviderService.java and move down to the onComplicationUpdate() method. Copy and paste the code below under the initial Log.d() call.

// Retrieve your data, in this case, we simply create a random number to display.
int randomNumber = (int) Math.floor(Math.random() * 10);

String randomNumberText =
       String.format(Locale.getDefault(), "%d!", randomNumber);

In this case, we are creating a simple random number to display. You could call your own methods to return anything you want.

Next we need to convert the data into a type the complication understands. In this case, we are converting it into the SHORT_TEXT data type.

A given data type may include different sets of fields. For example, SHORT_TEXT may be just a single piece of text, or a title and text, or an icon and text.

For our case, we are only setting the required field and none of the optional fields. To learn more about these types and fields, check out our documentation.

Copy and paste the code below under your new random number generator (from the previous step) in the same method.

ComplicationData complicationData = null;

switch (dataType) {
   case ComplicationData.TYPE_SHORT_TEXT:
       Log.d(TAG, "TYPE_SHORT_TEXT");
       complicationData = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
               .setShortText(ComplicationText.plainText(randomNumberText))
               .build();
       break;
   default:
       if (Log.isLoggable(TAG, Log.WARN)) {
           Log.w(TAG, "Unexpected complication type " + dataType);
       }
}

You are probably wondering why we are using a switch to create the data. Later, we will want to support various forms of the data based on the type the system is requesting. By using a switch now, we can easily add new data types (LONG_TEXT, RANGED_VALUE, etc.) later with minimal work.

Finally, now that we have the data in the right format, we must send the new data to the system. Copy and paste the following code below the code above.

if (complicationData != null) {
   complicationManager.updateComplicationData(complicationId, complicationData);
}

Your final method should look like this:

@Override
public void onComplicationUpdate(
       int complicationId, int dataType, ComplicationManager complicationManager) {
   Log.d(TAG, "onComplicationUpdate(): " + complicationId);

   // Retrieve your data, in this case, we simply create a random number to display.
   int randomNumber = (int) Math.floor(Math.random() * 10);

   String randomNumberText =
           String.format(Locale.getDefault(), "%d!", randomNumber);

   ComplicationData complicationData = null;

   switch (dataType) {
       case ComplicationData.TYPE_SHORT_TEXT:
           Log.d(TAG, "TYPE_SHORT_TEXT");
           complicationData = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
                   .setShortText(ComplicationText.plainText(randomNumberText))
                   .build();
           break;
       default:
           if (Log.isLoggable(TAG, Log.WARN)) {
               Log.w(TAG, "Unexpected complication type " + dataType);
           }
   }

   if (complicationData != null) {
       complicationManager.updateComplicationData(complicationId, complicationData);
   }
}

Run the app again

In the first step, you learned how to install your complication data service on your device or emulator. Now it's time to do that again! Install your app and reselect the complication, i.e., swipe watchface, select gear, navigate to same complication, and select the Random Number provider. You should see something like this:

Summary

In this step you've learned:

Next up

Let's try supporting a different data type.

Code step 3

While we expose our data to the complications, it might be nice to support more types of data, and to see what different data types look like in complications.

Specifying a different supported data type

Open the AndroidManifest.xml again and look at the declaration of the service RandomNumberProviderService.

Change the meta-data element SUPPORTED_TYPES from SHORT_TEXT to LONG_TEXT. Your change should now look like this:

<meta-data
    android:name="android.support.wearable.complications.SUPPORTED_TYPES"
    android:value="LONG_TEXT"/>

Adding support for LONG TEXT

Open RandomNumberProviderService.java, move down to the switch statement in onComplicationUpdate() method, and add the following code below the end of the TYPE_SHORT_TEXT case and above the default case.

case ComplicationData.TYPE_LONG_TEXT:
   Log.d(TAG, "TYPE_LONG_TEXT");
   complicationData = new ComplicationData.Builder(ComplicationData.TYPE_LONG_TEXT)
           .setLongText(
                   ComplicationText.plainText("Random Number: " + randomNumberText))
           .build();
   break;

The switch statement should now look something like this:

switch (dataType) {
   case ComplicationData.TYPE_SHORT_TEXT:
       Log.d(TAG, "TYPE_SHORT_TEXT");
       complicationData = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
               .setShortText(ComplicationText.plainText(randomNumberText))
               .build();
       break;
   case ComplicationData.TYPE_LONG_TEXT:
       Log.d(TAG, "TYPE_LONG_TEXT");
       complicationData = new ComplicationData.Builder(ComplicationData.TYPE_LONG_TEXT)
               .setLongText(
                       ComplicationText.plainText("Random Number: " + randomNumberText))
               .build();
       break;
   default:
       if (Log.isLoggable(TAG, Log.WARN)) {
           Log.w(TAG, "Unexpected complication type " + dataType);
       }
}

You've probably noticed by now that we are simply repackaging the same data in a new format. Let's see how it looks.

How to check your progress and debug

Install your service, but this time choose the Lower slot complication before choosing your complication service provider:

You should now see something like this:

Summary

In this step you've learned about:

Next up

We want to support one extra data type before putting it all together.

Code step 4

While we expose our data to the complications, let's continue exploring how to support more types of data.

Specifying a different supported data type

Open the AndroidManifest.xml again and take a look at the service RandomNumberProviderService.

Change the meta-data element SUPPORTED_TYPES to RANGED_VALUE. Your change should should now look like this:

<meta-data
    android:name="android.support.wearable.complications.SUPPORTED_TYPES"
    android:value="RANGED_VALUE"/>

Adding support for RANGED VALUES

Open RandomNumberProviderService.java, move down to the switch statement in the onComplicationUpdate() method, and add the code below the TYPE_LONG_TEXT case and above the default case.

case ComplicationData.TYPE_RANGED_VALUE:
   Log.d(TAG, "TYPE_RANGED_VALUE");
   complicationData = new ComplicationData.Builder(ComplicationData.TYPE_RANGED_VALUE)
           .setValue(randomNumber)
           .setMinValue(0)
           .setMaxValue(10)
           .setShortText(ComplicationText.plainText(randomNumberText))
           .build();
   break;

Your switch statement should now look like this:

switch (dataType) {
   case ComplicationData.TYPE_SHORT_TEXT:
       Log.d(TAG, "TYPE_SHORT_TEXT");
       complicationData = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
               .setShortText(ComplicationText.plainText(randomNumberText))
               .build();
       break;
   case ComplicationData.TYPE_LONG_TEXT:
       Log.d(TAG, "TYPE_LONG_TEXT");
       complicationData = new ComplicationData.Builder(ComplicationData.TYPE_LONG_TEXT)
               .setLongText(
                       ComplicationText.plainText("Random Number: " + randomNumberText))
               .build();
       break;
   case ComplicationData.TYPE_RANGED_VALUE:
       Log.d(TAG, "TYPE_RANGED_VALUE");
       complicationData = new ComplicationData.Builder(ComplicationData.TYPE_RANGED_VALUE)
               .setValue(randomNumber)
               .setMinValue(0)
               .setMaxValue(10)
               .setShortText(ComplicationText.plainText(randomNumberText))
               .build();
       break;
   default:
       if (Log.isLoggable(TAG, Log.WARN)) {
           Log.w(TAG, "Unexpected complication type " + dataType);
       }
}

Again, we are just repackaging the same data in a new format. Let's see how it looks.

How to check your progress and debug

Install your service, but this time choose the Right Dial complication before choosing your complication service provider:

You should now see something like this:

You can see a radial circle around the number that highlights the equivalent of 7/10.

Summary

In this step you've learned about:

Next up

We wrap up the code lab by enabling all the data type variations.

Code step 5

Right now our complication data provider supports three variations of our data (RANGED_VALUE, SHORT_TEXT, and LONG_TEXT).

In this last step, we will inform the system we support all three.

Specifying multiple supported data types

Open the AndroidManifest.xml again and look at the service RandomNumberProviderService.

Change the meta-data element SUPPORTED_TYPES to RANGED_VALUE,SHORT_TEXT,LONG_TEXT. Your change should now look like this:

<meta-data
    android:name="android.support.wearable.complications.SUPPORTED_TYPES"
    android:value="RANGED_VALUE,SHORT_TEXT,LONG_TEXT"/>

Check your progress

Install your service, but choose the Right Dial complication before choosing your complication service provider. You should see something like this:

In this case, the watch face prefers the ranged data type, but if this complication only supported the short text type, the data would still show up. Keep in mind the watch face itself specifies the data types that a complication supports, and the order of preference of those types.

Summary

In this step you've learned about:

You may decide you want to manually trigger updates to your complication data instead of waiting for the time to elapse you set in the manifest. You can do this in two ways:

  1. Updates all active complications associated with your data provider via providerUpdateRequester.requestUpdateAll()
  2. Update only a specific complication associated with your data provider via providerUpdateRequester.requestUpdate(yourComplicationId)

To see this in action, you can check out our WatchFace sample on GitHub. You will want to specifically look at UpdateComplicationDataService.java and RandomNumberProviderService.java.

Here is a snippet of the actual code you would use from the sample:

ComponentName componentName =
       new ComponentName(getApplicationContext(), YourComplicationProviderService.class);

ProviderUpdateRequester providerUpdateRequester =
       new ProviderUpdateRequester(getApplicationContext(), componentName);

if (yourComplicationId > 0) {
   // This method only updates the specific complication tapped on the watch, if you
   // wanted to update all active complications associated with your data, you would
   // call providerUpdateRequester.requestUpdateAll().
   providerUpdateRequester.requestUpdate(yourComplicationId);
}

For more details on developing complications for watch faces and creating complication data providers, visit Watch Face Complications

For more details about developing Android Wear watch faces, visit https://developer.android.com/training/wearables/watch-faces/index.html