651.288.7000 info@intertech.com

In a previous post in this Android Wear Developer Tutorial, we talked about how to extend an app using bridged notifications to provide functionality on devices running Android Wear.  Using NotificationCompat.WearableExtender, we were able to add some nice features without having to directly write any code that runs on the wearable device.  This allows us to integrate our app with Android Wear at a relatively low cost.

However, sometimes you want (or need) to do more.  Using bridged notifications, we were able to leverage the Android Wear concept of Suggest to provide useful information at a relevant time.  In addition to Suggest, creating an app that runs directly on the wearable device allows our app to respond to the user’s request.  This is the concept of Demand; we can provide information whenever it is requested.

Continuing the example of our Time Entry application, in this Android Wear Developer Tutorial, we will create a standalone wearable app to display the total time entered for the day and for the week.  Creating a standalone app will enable us to use custom layouts to display our information in a more sophisticated manner than simply a line of text in a notification.

Setup

  • This tutorial assumes that you are using Android Studio.

First, make sure your SDK tools are version 23.0.0 or higher.  Then, add a new module for Android Wear.  In Android Studio: File -> New -> New Module and select Android Wear Module (if you are creating a new project, select Android Wear as one of the targets).  Note that the minimum SDK for Android Wear is API 20.  When prompted, select the Activity that is most appropriate.  For this case, I used Blank Wear Activity.

Wearable Standalone App Overview

Creating the components for wearable apps is not really any different than for a handheld app.  The biggest difference is the screen size.  Here I am going to create a single Activity that uses a GridViewPager that contains two Fragments: one that displays daily Time Entry information and another for weekly.  GridViewPager is a very useful View for wearables, as it allows the user to navigate multiple fragments by swiping left and right, similar to how multiple pages work on wearable notifications.

The fragments inside the GridViewPager will use a BoxInsetLayout as the root layout.  Using this view allows you to create a single view that will work well on both round and rectangular screens.  Another option for a root layout would be a WatchViewStub.  This view allows you to specify separate layouts for round and rectangular screens, and will take care of inflating the correct one.

  • For more UI options, see here.

Here is what the finished product will look like:

Daily Summary

Daily Summary

Weekly Summary

Weekly Summary

 

 

The graph on the bottom is the same view that I created in part 2 of Android Custom View Tutorial.  In that tutorial, I mentioned the importance of using custom attributes when creating custom views, because that makes your view much easier to reuse.  Here is a great example of that: all I had to do is drop the custom view in my layout, set the attributes, and it looks great.

The Android Wear Data Layer

Our app needs a way to get data from our application server, where the Time Entry data resides.  Our mobile app already contains that functionality in the form of a REST client, but how do we get that data from the mobile app to the wearable app?  The answer is the Wearable Data Layer.

The Wearable Data Layer provides a few different ways to send data between wearable and handheld devices.  The two that we are going to use are Messages and Data Items.  Messages provide one way communication between devices.  Data items are used to sync data between devices.

Here is an overview of how our app will communicate using the data layer:

  1. The wearable will send a message to the handheld app, requesting data.
  2. The handheld will listen for messages on the data layer.  When the message is received, it will retrieve the data from the server.
  3. The handheld will place the data on the data layer as a data item.
  4. The wearable will listen for data change events on the data layer.  When the event is received, it will use the data to update it’s UI.

Data Layer Messages

Sending a message basically involves three steps:

  1. Connect to Google API.
  2. Determine the node (or nodes) to send the message to.
  3. Send the message.

That doesn’t seem too tough, but it can get a little tricky when you realize that each of these operations are asynchronous.  For example, the call to GoogleApiClients’s connect method will return immediately and perform the connection in the background.  This mean that you need to provide a listener with callbacks for success or failure.  Then, in the callback for handling a successful connection, you initiate the asynchronous call to get connected nodes, which also requires callbacks.  Finally, once the proper node (or nodes) is determined, you can then send the message, which is also asynchronous.  This is not a fun way to live.  Nesting callbacks in this manner can be very difficult to manage, a condition sometimes referred to as “callback hell”.

All of these operations also provide a synchronous option.  For example, in the case of GoogleApiClient, you can call blockingConnect() instead of connect().  While this avoids the nested callbacks, using synchronous calls also has some complications.  First of all, you can’t use these synchronous operations on the Android main thread; you need to use a background thread.  Secondly, it can result in a lot of boilerplate code: perform the first operation, check for success or failure.  Perform the second operation, check for success or failure.  Perform the third operation… you get the idea.

You don’t need to exclusively choose synchronous or asynchronous.  For example, you could create a component that connects to GoogleApiClient when it is created (e.g. in the onCreate method), and implements GoogleApiClient.ConnectionCallbacks to provide callbacks for when the client connects or disconnects.  That way, you always know whether or not Google APIs are available; you don’t have to try to establish a new connection every time you need to send a message.

The best approach to use can depend on many factors.  The point here is that you should think about what makes sense given your circumstances.  If you have an Activity that is potentially going to send many messages, it might be best to establish a connection to GoogleApiClient when the Activity is created, and then keep track of capable nodes by implementing CapabilityApi.CapabilityListener.  That way, when you need to send a message, you already know what node/nodes to send the message to.

Here’s the approach that I am going to use: instead of keeping track of the Google API connection status and capable nodes in the Activity, I am going to use an Android Service class.  Any UI component that needs access to the Data Layer can bind to this service.  It seems to me that communication with the Data Layer rightly belongs in a separate tier than our activities and fragments.  These components can focus on managing views and UI flow, and our service class can handle the dirty work of communicating with the Data Layer.  Also, an Android Service class allows multiple components to bind to it at any time.  This means, for example, that if we ever have multiple fragments that need access to the Wearable Data Layer, we still only have a single component that manages the connection to Google API and keeps track of capable nodes.

  • Note: I am only going to show code specific to the wearable data layer, since that is the topic of this tutorial.  I am not going to show code for binding to the Service class, for example, since that is a broader Android topic.

Connecting to Google API and Keeping Track of Nodes

Our service class will do three things:

  1. Manage the connection to GoogleApiClient.
  2. Keep track of nodes.
  3. Provide a Binder to allow bound components to send a message on the Data Layer.

First, a few comments about #2: keeping track of nodes.  Android provides two ways to do this: NodeApi and CapabilityApi.  NodeApi is useful if you want info about all connected nodes.  However, we want to get a specific node: the one that is capable of fetching data for our application (i.e. the mobile handheld device, which is running the full version of our app).  With NodeApi, we are not able to easily identify which node is the handheld.  The CapabilityApi allows us to identify nodes with a specific capability, which we define.

To do so, we need to update the handheld module to advertise the capability to retrieve Time Entry data.  Create an XML file in the res/values/ directory and name it wear.xml.  In that file, add a resource named android_wear_capabilities, and include an item to represent the capability being defined:

This simple step allows us to use the CapabilityApi to identify the handheld device, which is capable of retrieving time entry data.

Ok, now back to #1.  For managing the connection to GoogleApiClient, our Service class is going to implement GoogleApiClient.ConnectionCallbacks.  In the onCreate method, we will create an instance of GoogleApiClient and initiate an asynchronous call to connect:

When a connection is made, onConnected is called (if you want to also handle failed connection attempts, you need to provide a GoogleApiClient.OnConnectionFailedListener).  In the onConnected method, we make the asynchronous call to get capable nodes, and add our instance as a capability listener.  This means that our service class also needs to implement CapabilityApi.CapabilityListener.  This allows us to be notified when there is a change to capable nodes (a capable node connects or disconnects).  Here is the onConnected method:

  • note that we are using the same String (fetch_timeentry_data_capability) that we declared as a capability in the handheld app

The other method in GoogleApiClient.ConnectionCallbacks that we need to implement is onConnectionSuspended.  Here we are going to remove our class as a capability listener, and set nodeForTimeEntry to null.  This variable is declared as a class member for keeping track of the node to use for retrieving Time Entry data.  Here is the onConnectionSuspended method:

Since our class implements CapabilityApi.CapabilityListener, we need to override onCapabiltyChanged:

This simply calls determineNodeForDataRetrieval.  This is the same method used in the callback when we first retrieved capable nodes, right after the connection to GoogleApiClient was successfully established.  This method is where we keep track of the node to use for retrieving Time Entry data:

Now, when a UI component binds to our service and requests to send a message, all we have to check is check if nodeForTimeEntry is defined.

Sending the Message

Since we already have a connected GoogleApiClient, and we already know what node to send the message to, all we need to do is send the message.  A message is identified by a path (i.e. a String) and may contain an optional payload, which must be a byte array.  For the current case, a payload is not necessary, since we are simply notifying the handheld app that we need data.  However, I am going to include a value that represents the date for which to retrieve data.  That way, if my wearable app ever needs data for anything other than the current date, the logic will already be in place.

Note that when sending the message, this code calls the await() method.  This is a synchronous call, and therefore can’t be run on the main thread.  In this case, I am simply wrapping this inside a Runnable and then using new Thread(runnable).start();  I think this is ok for a simple case.  If you expect that your app will be continually sending many message, you will probably want to use a better mechanism such as a thread pool or IntentService.

Receiving the Message

The handheld app needs to listen for messages on the data layer.  Android provides a class that we can extend for listening to Data Layer events: WearableListenerService.  To listen for messages, override onMessageReceived:

As you can see, WearableListenerService is pretty easy to use.  When a message arrives for the app, the system will take care of binding to this service and calling onMesageReceived.  To allow the system to bind to the service, it needs to be declared in the manifest:

Note that WearableListenerService does its work on a background thread.  This means that we are free to use synchronous calls to retrieve data from the application server and place it on the Data Layer.

  • Retrieving data from the application server is not covered here, since that is specific to each application.

Placing Data on the Data Layer

Once we have retrieved the necessary data, we can place it on the Data Layer so that it is available to the wearable app.  Similar to sending a message, we first need to create and connect to a GoogleApiClient.  Since we are running on a background thread, we can use the synchronous method blockingConnect:

The easiest way to put data on the Data Layer is using a PutDataMapRequest.  This gives you access to a DataMap object on which you can set data as name/value pairs.  Then you can call asPutDataRequest to create a PutDataRequest which you can use to place the data on the data layer using the wearable DataApi.

One thing to note about DataMap is that it only supports certain types, which are limited to (more or less) primitives and collections of primitives.  There is no easy way to place a model object on a DataMap.  If you want to place an object on a DataMap, it needs to be converted to name/value pairs.

In the case of Time Entry data, since I am only interested in the total time entered for the current day and week, I could simply place those two values on the data map and be done with it.  However, I decided that I wanted to have all of the data available on the data layer, in case I ever want to do something more with it on the wearable (a time entry object consists of more than simply a numeric value; it also contains a project name, date, detailed description, and more).

I have not been able to find a great solution for this situation.  I ended up adding methods to my model object for converting it to/from a data map.  For example:

Since I am going to be placing several objects on the data map (i.e. all entries for the current week), I convert them all to data maps and place them in a List.  Then I can use putDataMapArrayList to place the entire list on the original data map.  Here is the code that does all of this:

The data is put on the data layer using Wearable.DataApi.putDataItem().  Since we are in a background thread, I am calling await() on the returned PendingResult object to execute synchronously.

Listening for Data Events

With our data on the data layer, now we need to update our wearable app to be able to use it.  Listening for data events is just like listening for messages, which we just configured the handheld app to do.  Similarly, we are going to use a WearableListenerService, but instead of overriding onMessageReceived(), we need to override onDataChanged().  Given what we have covered so far, this won’t be too difficult.  We need to 1) connect to GoogleApiClient, and 2) read the data from the data layer.  As already mentioned, WearableListenerService does its work in a background thread, so we can use synchronous calls.

We get the data map attached to the event, and then convert the list of data maps to a list of time entries.  We then use a LocalBroadcastManager to broadcast the entries (this occurs in the broadcastEntries method, which is not shown).  Our UI components will listen to this broadcast message to be notified when data is available.  If the entries were deleted for the data layer, we broadcast an empty list.

Creating the UI Components

Finally, we are ready to create the UI components.  Android Studio has already created us an Activity, as well as a manifest that declares the Activity so that it can be launched as a standalone app.  I am going to update that Activity to use a GridViewPager that has two Fragments:

This class extends DataLayerAwareActivity, which contains the details for binding to the Service that we created earlier.  This service keeps track of capable nodes and provides a Binder for sending messages on the Data Layer (the code for this class is not shown in this tutorial).

The Layout for the Activity is very simple.  It consists of a full screen GridViewPager:

All that remains is to create the fragments for daily and weekly Time Entry data, which will populate the GridViewPager.  Since this does not involve anything specific to Android Wear, I am not going to show these details here.  However, I will note a few things.

First, the fragments will need access to the PagerActivity (which extends DataLayerAwareActivity), since that is how we  access the Service for the Data Layer.  I will enforce this relationship in the Fragment’s onAttach method:

Second, remember that we are using a LocalBroadcastManager to broadcast time entry data when it has been retrieved from the data layer.  Thus, we need to use a BroadcastReceiver in our fragments to receive that broadcast.  When the broadcast is received, we can use the time entry data to update the fragment’s views.

Responding to Voice

As mentioned previously, creating a standalone wearable app means that a user can start our app on demand from a wearable device.  To allow our app to be started using voice, we simply need to add a label to our Activity’s declaration in the manifest:

Our app will be started when the user says “Start Time Entry” from the cue card.

Wrap Up

Creating a standalone app for wearable devices does have a learning curve.  Namely, you need to be able to work effectively with the Wearable Data Layer.  Once you understand the data layer, you will find that writing wearable apps is not a whole lot different from a typical Android application.

If you’re just finding this Android Wear Developer tutorial now, here are a couple links to the rest of the posts in the series:

Android Wear Developer Tutorial (Part 1) – Introduction and Overview

Android Wear Developer Tutorial (Part 2) – Bridged Notifications

Android Wear Developer Tutorial (Part 4) – Wearable Only Notification With a Custom Layout

Android Wear Developer Tutorial (Part 5) – Tips and Tricks

Like What You've Read?

Subscribe to the Blog.

Every Friday we send that week's content from our Developers via email. Try it out!

Some ad blockers can block the form below.

You have Successfully Subscribed!

Pin It on Pinterest

Share This