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.
Open the AndroidManifest.xml
file and look at the service CustomComplicationProviderService
. 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) we support. In this case, we support SMALL_IMAGE, but for this step, change that to SHORT_TEXT. Change your first meta-data
element to the following:
<meta-data
android:name="android.support.wearable.complications.SUPPORTED_TYPES"
android:value="SHORT_TEXT"/>
As stated earlier, onComplicationActivated()
is called when your data provider is activated. This is a good place/time to perform any basic setup that needs to be done once per activation.
However, onComplicationUpdate()
is where the active complication requests updated data.
The method onComplicationUpdate()
is triggered for various reasons:
ProviderUpdateRequester.requestUpdate()
methodOpen CustomComplicationProviderService.java
and move the cursor down to the onComplicationUpdate()
method. Copy and paste the code below under the initial Log.d()
call:
// Used to create a unique key to use with SharedPreferences for this complication.
ComponentName thisProvider = new ComponentName(this, getClass());
// Retrieves your data, in this case, we grab an incrementing number from SharedPrefs.
SharedPreferences preferences =
getSharedPreferences(
ComplicationTapBroadcastReceiver.COMPLICATION_PROVIDER_PREFERENCES_FILE_KEY,
0);
int number =
preferences.getInt(
ComplicationTapBroadcastReceiver.getPreferenceKey(
thisProvider, complicationId),
0);
String numberText = String.format(Locale.getDefault(), "%d!", number);
In this case, we are getting a stored int
in a SharedPreference
that represents our data. This could easily be a call to your database.
We also convert it to a simple string in preparation for converting it to a ComplicationData
object.
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 no optional fields. To learn more about these types and fields, review our documentation.
Copy and paste the code below under the new code you added earlier to retrieve our integer in the same method.
ComplicationData complicationData = null;
switch (dataType) {
case ComplicationData.TYPE_SHORT_TEXT:
complicationData =
new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
.setShortText(ComplicationText.plainText(numberText))
.build();
break;
default:
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Unexpected complication type " + dataType);
}
}
You may ask why we are using a switch to create the data. Later, we will 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.
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 after the code above.
if (complicationData != null) {
complicationManager.updateComplicationData(complicationId, complicationData);
} else {
// If no data is sent, we still need to inform the ComplicationManager, so the update
// job can finish and the wake lock isn't held any longer than necessary.
complicationManager.noUpdateRequired(complicationId);
}
Your final method should look like this:
@Override
public void onComplicationUpdate(
int complicationId, int dataType, ComplicationManager complicationManager) {
Log.d(TAG, "onComplicationUpdate() id: " + complicationId);
ComponentName thisProvider = new ComponentName(this, getClass());
SharedPreferences preferences = getSharedPreferences( ComplicationTapBroadcastReceiver.COMPLICATION_PROVIDER_PREFERENCES_FILE_KEY, 0);
int number =
preferences.getInt(
ComplicationTapBroadcastReceiver.getPreferenceKey(
thisProvider, complicationId),
0);
String numberText = String.format(Locale.getDefault(), "%d!", number);
ComplicationData complicationData = null;
switch (dataType) {
case ComplicationData.TYPE_SHORT_TEXT:
complicationData =
new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
.setShortText(ComplicationText.plainText(numberText))
.build();
break;
default:
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Unexpected complication type " + dataType);
}
}
if (complicationData != null) {
complicationManager.updateComplicationData(complicationId, complicationData);
} else {
// If no data is sent, we still need to inform the ComplicationManager, so
// the update job can finish and the wake lock isn't held any longer.
complicationManager.noUpdateRequired(complicationId);
}
}
In the first step, you learned 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 the watch face, select the gear, navigate to the same complication, and select the Complications Data Provider Codelab. You should see something like this:
In this step you've learned:
Let's try supporting a different data type.
In this step, we will trigger updates in the data when the user taps our complication.
If at any point you are confused by the concepts discussed here, please refer to the "3-trigger-updates" module and see how these steps may be implemented.
Open the AndroidManifest.xml
file and look again at the service CustomComplicationProviderService
.
Notice an UPDATE_PERIOD_SECONDS field in the meta-data
element. This specifies how often you want the system to check for updates to the data when your data provider is active.
Right now, it is set to 600 seconds (10 minutes). If you are using UPDATE_PERIOD_SECONDS, we recommend setting updates in order of minutes. Note that this value is only guidance for the system. Wear OS may update less frequently.
A better approach is a "push style" where we tell the system only when the data has changed.
Change the update frequency from 600 to 0, since we will be telling the system only when data has changed. Note that the meta-data is a requirement.
<meta-data
android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
android:value="0"/>
Open ComplicationTapBroadcastReceiver.java
. This BroadcastReceiver
class updates our complication data when it's triggered. (Remember we are just saving the data to a SharedPreference
.)
The class also offers several helper methods that construct a PendingIntent
(triggers it as a BroadcastReceiver
) and create unique preference keys for our complication (used with SharedPreference
).
Right now, it only updates the integer in SharedPreference
. We need to tell our complication that the data has been updated.
Move to the bottom of the onReceive()
method. Copy and paste the code below the editor.apply()
call.
// Request an update for the complication that has just been tapped.
ProviderUpdateRequester requester = new ProviderUpdateRequester(context, provider);
requester.requestUpdate(complicationId);
This instructs Wear OS that our complication's data has been updated. We need three pieces of data for this to work:
Context
is available as an argument for this method: onReceive()
.ComponentName
of your complication is passed in as an Extra
from the PendingIntent
that triggers this BroadcastReceiver
.int
is passed in as an Extra
from the PendingIntent
that triggers this BroadcastReceiver
.We are done with this step and your final method should look like this:
@Override
public void onReceive(Context context, Intent intent) {
Bundle extras = intent.getExtras();
ComponentName provider = extras.getParcelable(EXTRA_PROVIDER_COMPONENT);
int complicationId = extras.getInt(EXTRA_COMPLICATION_ID);
// Retrieve data via SharedPreferences.
String preferenceKey = getPreferenceKey(provider, complicationId);
SharedPreferences sharedPreferences =
context.getSharedPreferences(COMPLICATION_PROVIDER_PREFERENCES_FILE_KEY, 0);
int value = sharedPreferences.getInt(preferenceKey, 0);
// Update data for complication.
value = (value + 1) % MAX_NUMBER;
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putInt(preferenceKey, value);
editor.apply();
// Request an update for the complication that has just been tapped.
ProviderUpdateRequester requester = new ProviderUpdateRequester(context, provider);
requester.requestUpdate(complicationId);
}
Our BroadcastReceiver
not only updates the data but also informs the system that new data is available (see the previous step). We need to add a tap action to our complication to trigger the BroadcastReceiver
.
Open CustomComplicationProviderService.java
and move down to the onComplicationUpdate()
method.
Find the this line of code (the first couple of lines of the onComplicationUpdate()
method).
ComponentName thisProvider = new ComponentName(this, getClass());
Recall from the previous step that this was one of the pieces of data our BroadcastReceiver
needed to inform the system that our complication had new data. The other piece of data is the complication ID, which is passed into this method. We need to create a PendingIntent
that passes both those values along an Extra
.
Luckily, ComplicationTapBroadcastReceiver
provides this as a helper method.
Copy and paste the code below under the line of code you found earlier:
// We pass the complication id, so we can only update the specific complication tapped.
PendingIntent complicationPendingIntent =
ComplicationTapBroadcastReceiver.getToggleIntent(
this, thisProvider, complicationId);
Next we need to assign the PendingIntent
to the tap event for our complication.
Find the switch statement and replace the case for ComplicationData.TYPE_SHORT_TEXT
with the code below.
case ComplicationData.TYPE_SHORT_TEXT:
complicationData =
new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
.setShortText(ComplicationText.plainText(numberText))
.setTapAction(complicationPendingIntent)
.build();
break;
This only adds one line, the .setTapAction()
method which assigns our new PendingIntent
to the tap action for the complication.
We are done with this step. Your final method should look like this:
@Override
public void onComplicationUpdate(
int complicationId, int dataType, ComplicationManager complicationManager) {
Log.d(TAG, "onComplicationUpdate() id: " + complicationId);
// Create Tap Action so that the user can trigger an update by tapping the complication.
ComponentName thisProvider = new ComponentName(this, getClass());
// We pass the complication id, so we can only update the specific complication tapped.
PendingIntent complicationPendingIntent =
ComplicationTapBroadcastReceiver.getToggleIntent(
this, thisProvider, complicationId);
// Retrieves your data, in this case, we grab an incrementing number from SharedPrefs.
SharedPreferences preferences =
getSharedPreferences(
ComplicationTapBroadcastReceiver.COMPLICATION_PROVIDER_PREFERENCES_FILE_KEY,
0);
int number =
preferences.getInt(
ComplicationTapBroadcastReceiver.getPreferenceKey(
thisProvider, complicationId),
0);
String numberText = String.format(Locale.getDefault(), "%d!", number);
ComplicationData complicationData = null;
switch (dataType) {
case ComplicationData.TYPE_SHORT_TEXT:
complicationData =
new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
.setShortText(ComplicationText.plainText(numberText))
.setTapAction(complicationPendingIntent)
.build();
break;
default:
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Unexpected complication type " + dataType);
}
}
if (complicationData != null) {
complicationManager.updateComplicationData(complicationId, complicationData);
} else {
// If no data is sent, we still need to inform the ComplicationManager,
// so the update job can finish and the wake lock isn't held any longer
// than necessary.
complicationManager.noUpdateRequired(complicationId);
}
}
Install your app and reselect the complication, i.e., swipe the watch face, select the gear, navigate to the same complication, and select the Complications Data Provider Codelab provider. You should see the same thing as you saw before. However, now you can tap the complication and the data will be updated.
In this step you've learned:
PendingIntent
to a tap action on your complicationLet's try supporting a different data type.
As we expose our data to complications, it might be nice to support more types of data, and to see what different data types look like in complications.
Open the AndroidManifest.xml
file again and look at the declaration of the service CustomComplicationProviderService
.
Change the meta-data
element SUPPORTED_TYPES
from SHORT_TEXT to LONG_TEXT. Your change should look like this:
<meta-data
android:name="android.support.wearable.complications.SUPPORTED_TYPES"
android:value="LONG_TEXT"/>
Open CustomComplicationProviderService.java
, move down to the switch statement in the 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:
complicationData =
new ComplicationData.Builder(ComplicationData.TYPE_LONG_TEXT)
.setLongText(ComplicationText.plainText("Number: " + numberText))
.setTapAction(complicationPendingIntent)
.build();
break;
The switch
statement should look something like this:
switch (dataType) {
case ComplicationData.TYPE_SHORT_TEXT:
complicationData =
new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
.setShortText(ComplicationText.plainText(numberText))
.setTapAction(complicationPendingIntent)
.build();
break;
case ComplicationData.TYPE_LONG_TEXT:
complicationData =
new ComplicationData.Builder(ComplicationData.TYPE_LONG_TEXT)
.setLongText(ComplicationText.plainText("Number: " + numberText))
.setTapAction(complicationPendingIntent)
.build();
break;
default:
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Unexpected complication type " + dataType);
}
}
You may have noticed that we are simply repackaging the same data in a new format. Let's see how it looks.
Install your service, but this time choose the bottom slot complication before choosing your complication service provider:
You should see something like the image below. Note that each complication is stored under a separate key, so you might see different values if you have set the complication in multiple locations:
In this step you've learned about:
We want to support one extra data type before putting it all together.
While we expose our data to complications, let's continue exploring how to support more types of data.
Open the AndroidManifest.xml
file again and take a look at the service CustomComplicationProviderService
.
Change the meta-data
element SUPPORTED_TYPES
to RANGED_VALUE. Your change should should look like this:
<meta-data
android:name="android.support.wearable.complications.SUPPORTED_TYPES"
android:value="RANGED_VALUE"/>
Ranged values can not only show text but also display a visual showing how far your value is between the minimum and maximum value. This type of of complications is good for showing how much battery is left on the device or how many steps you have left to meet you goal.
Open CustomComplicationProviderService.java
, move your cursor down to the switch statement in the onComplicationUpdate()
method, and add this code under the TYPE_LONG_TEXT
case and above the default case:
case ComplicationData.TYPE_RANGED_VALUE:
complicationData =
new ComplicationData.Builder(ComplicationData.TYPE_RANGED_VALUE)
.setValue(number)
.setMinValue(0)
.setMaxValue(ComplicationTapBroadcastReceiver.MAX_NUMBER)
.setShortText(ComplicationText.plainText(numberText))
.setTapAction(complicationPendingIntent)
.build();
break;
Your switch statement should look like this:
switch (dataType) {
case ComplicationData.TYPE_SHORT_TEXT:
complicationData =
new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
.setShortText(ComplicationText.plainText(numberText))
.setTapAction(complicationPendingIntent)
.build();
break;
case ComplicationData.TYPE_LONG_TEXT:
complicationData =
new ComplicationData.Builder(ComplicationData.TYPE_LONG_TEXT)
.setLongText(ComplicationText.plainText("Number: " + numberText))
.setTapAction(complicationPendingIntent)
.build();
break;
case ComplicationData.TYPE_RANGED_VALUE:
complicationData =
new ComplicationData.Builder(ComplicationData.TYPE_RANGED_VALUE)
.setValue(number)
.setMinValue(0)
.setMaxValue(ComplicationTapBroadcastReceiver.MAX_NUMBER)
.setShortText(ComplicationText.plainText(numberText))
.setTapAction(complicationPendingIntent)
.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.
Install your service, and choose another location.
You should now see something like this:
You can see a radial circle around the number that highlights the equivalent of 5/20.
In this step you've learned about:
We wrap up the code lab by enabling all the data type variations.
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 variations.
Open the AndroidManifest.xml
file again and look at the service CustomComplicationProviderService
.
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"/>
Install your service.
In this case, the watch face prefers the ranged data type over the short and long types, but if the complication only supported the short text type, the data would still show up because the watch face supports all three data types. Keep in mind the watch face itself specifies the data types that a complication supports, and the order of preference of those types.
In this step you've learned about:
There are many more data types you can support in complications (including small images, large images, and icons). Try to extend this code lab by implementing some of those types on your own.
For more details on developing complications for watch faces and creating complication data providers, visit Watch Face Complications
For more details about developing Wear OS watch faces, visit https://developer.android.com/training/wearables/watch-faces/index.html
Also watch these great videos: