Communicating with wearables

Wearable apps provide a great way to extend handheld devices; however, sometimes, a wearable is not powerful enough to perform a task on its own, or it needs information from the handheld.

Getting ready

As we are going to be communicating between the devices using Google Play services, we need to install the Xamarin.GooglePlayServices.Wearable NuGet before we start.

How to do it...

Apps running on wearables can communicate with apps running on the handheld devices using the Google Play services. To implement data communication between the handheld and the wearable, we can actually use the same code:

  1. We are going to make use of the Google Play services' Data API to synchronize data between the devices. First, we need to implement the IDataApiDataListener interface in our activity:
    public class MainActivity : Activity, IDataApiDataListener {
      public void OnDataChanged(DataEventBuffer dataEvents) {
      }
    }
  2. Then, we need to create an instance of the Google Play services' API client, usually in the OnCreate() method of the activity. We use various callbacks to add and remove the Data API listener:
    apiClient = new GoogleApiClientBuilder(this)
      .AddConnectionCallbacks(bundle => {
        // attach the data listener
        WearableClass.DataApi.AddListener(apiClient, this);
      }, cause => {
        // detach the listener
        WearableClass.DataApi.RemoveListener(apiClient, this);
      })
        .AddOnConnectionFailedListener(result => {
          // handle errors
      })
      .AddApi(WearableClass.API)
      .Build();
  3. Now that we have the client, we can initiate the connection to the backing service by invoking the Connect() method of the client. This is typically done in the OnResume() method of the activity when we are in the foreground:
    protected override void OnResume() {
      base.OnResume();
    
      apiClient.Connect();
    }
  4. To ensure that we clean up when the user exits our app, we remove the Data API listener and disconnect it from the services in the OnPause() method:
    protected override void OnPause(){
      if (apiClient != null &&apiClient.IsConnected) {
        WearableClass.DataApi.RemoveListener(apiClient, this);
      apiClient.Disconnect();
      }
    
      base.OnPause();
    }

Once we are connected, we can start updating our app's data using the Data API. These updates are then synchronized with the handheld and wearable:

  1. Data is synchronized using a set of key-value pairs at a virtual location. We need to specify a string key for the data member, and a string path to the entire data object:
    private const string CountKey ="keys.count";
    private const string CounterPath = "/games/counter";
  2. To create the data request that will be sent over the Data API, we make use of the DataMap and PutDataMapRequest types:
    var mapRequest = PutDataMapRequest.Create(CounterPath);
    mapRequest.DataMap.PutInt(CountKey, count);
  3. Then, we send the new DataMap instance over the Data API using the PutDataItemAsync() method:
    var dataRequest = mapRequest.AsPutDataRequest();
    var dataApi = WearableClass.DataApi;
    await dataApi.PutDataItemAsync(apiClient, dataRequest);
  4. As soon as the data has synchronized, we will receive an event on the other device via the OnDataChanged() method of the IDataApiDataListener interface:
    public void OnDataChanged(DataEventBuffer dataEvents) {
      if (dataEvents.Status.IsSuccess) {
        // get the value from the event
        foreach (var dataEvent in dataEvents) {
          if (dataEvent.Type == DataEvent.TypeChanged) {
            var item = dataEvent.DataItem;
            if (item.Uri.Path == CounterPath) {
              var map = DataMapItem.FromDataItem(item).DataMap;
              count = map.GetInt(CountKey);
              break;
            }
          }
        }
    
        // make sure we update on the UI thread
        RunOnUiThread(() => {
          // update the UI
        });
      } else {
        // handle errors
      }
    }
  5. When we start the app for the first time after the connection has succeeded, we might want to get hold of the existing data:
    var dataApi = WearableClass.DataApi;
    var dataItems = await dataApi.GetDataItemsAsync(apiClient);
    foreach (var item in dataItems) {
      if (item.Uri.Path == CounterPath) {
        var dataMap = DataMapItem.FromDataItem(item).DataMap;
        count = dataMap.GetInt(CountKey);
        break;
      }
    }

How it works...

Creating a wearable app is a great way to extend the handheld and provide an additional user experience. However, the device is often much more limited in hardware, most certainly so with regards to the screen size.

One of the great ways we can extend the handheld experience, but without losing on the power of the handheld, is by establishing communication between the devices. By communicating with a handheld, the wearable is able to offload any intensive or complex tasks to a more capable device.

Tip

Wearable apps can communicate with their handheld counterparts in many ways.

The devices can communicate using several methods, and one of the easiest and most common methods is to synchronize the data, such as app settings or user preferences. This type of communication or synchronization uses the Google Play services' Data API.

The Data API not only synchronizes data between the wearable and handheld, but also with the cloud and any other devices connected to the cloud. To use the Data API in an Android wearable app, we must first install the Xamarin.GooglePlayServices.Wearable NuGet, which will install the prerequisites.

By storing the data on the cloud, our app can preserve its data when the user gets new devices, as well as provide a persistent storage option for the app data. Another benefit of cloud storage is that if the wearable loses its connectivity temporarily, the wearable can retrieve the data from the cloud when it reconnects.

Note

Google Play services synchronize data with all the connected devices, both those connected directly and those connected through the cloud.

To communicate between devices, we need to set up the Google API client. This client manages the connections and events, providing a simple, listener-based notification system. In addition to the Data API events, we are notified of connection events, such as when the connection is established or if it goes down temporarily.

The Data API has an event to notify listeners when new data is synchronized or changed on one of the devices. Since we want to know when the handheld or wearable changes the data, we must implement the IDataApiDataListener interface. It is not necessary to create a new object for this, and therefore, the current activity can be used.

The IDataApiDataListener interface has a single method—OnDataChanged(). This method is invoked as soon as the client is informed about another device changing the data. If the wearable is connected via Bluetooth, then the data and events come through the Bluetooth stack. Other devices receive events through the cloud, possibly over Wi-Fi.

Note

The IDataApiDataListener interface is used when listening for making changes to the data.

Before we can transfer data, we need to connect to the Google Play services. To create the client, we instantiate a new GoogleApiClientBuilder instance.

Because we care about the connection status, we need to pass two delegates to the AddConnectionCallbacks() method. The first method is invoked when the client connection succeeds. Here, we make sure to attach our IDataApiDataListener interface instance to the Data API. The second delegate is invoked when the connection is suspended. In this delegate, we make sure to detach our interface instance so that no memory is leaked.

The other connection event method is the AddOnConnectionFailedListener() method. This method allows us to attach a delegate that will be invoked if the connection fails.

Tip

The Google Play services client uses callbacks to notify about connection status changes.

Finally, before we create the client instance, we specify that we are going to be using the wearable APIs for communication with the wearable. We do this by passing the value of the API property of the WearableClass to the AddApi() method. The WearableClass type contains various properties that are used to access various APIs used for communication with wearables. The API property contains a token used by the client to set up the wearable APIs.

After a client has been connected, we can use the DataApi property of the WearableClass type to access the data APIs for wearables. The DataApi property returns an instance of the IDataApi interface, which we can use to attach and detach the listeners relating to the wearable Data API.

Tip

The WearableClass type contains various properties that are used to communicate with wearables.

The IDataApi interface has several methods, but we only need the AddListener() and RemoveListener() methods. We pass the current client and the listener instance to each of these methods, making sure to attach the listener when the client connects, and detaching it when the connection is suspended.

We invoke the Connect() method of the client in OnResume, as we want to connect only when our activity is in the foreground. Conversely, we invoke the Disconnect() method when we leave the foreground or when the OnPause() method is invoked. We should detach the listener before we disconnect so that we do not leak any memory.

Once we are connected, we can start sending data to the service. The service will then process and synchronize the data to various devices and the cloud. The easiest way to manage the data is using the DataMap type. A DataMap instance is stored at a specific path and consists of key-value pairs. There are various put and get methods, such as PutString and GetString, which are used to store and retrieve data.

Even though we are using the DataMap type to store our data, the Data API expects the DataItem types. When storing and retrieving data, the DataMap type is serialized and deserialized into a DataItem type, which stores the DataMap type as a byte array.

Note

The Data API handles the DataItem types, which are deserialized into and serialized from the DataMap types.

When we want to store a new data map, we create an instance of PutDataMapRequest using the static Create() method. The PutDataMapRequest instance is a container type for a DataMap instance and the path to the data map. To access or update the data map that will be stored, we use the DataMap property.

The Create() method requires that we specify the path where we are going to store the data map. The path is similar to a Unix-based, file-system path. It uses forward slashes and must begin with a forward slash. Although similar to a file system, it is not actually stored at this location on the local file system.

Note

The PutDataMapRequest.Create() method requires that the path start with a forward slash.

Before we can synchronize our data map, we must obtain the data item from our PutDataMapRequest using the AsPutDataRequest() method. This method returns another container object, a PutDataRequest instance, which now contains the serialized byte array that was once a DataMap type.

We can now pass the new PutDataRequest instance to the PutDataItemAsync() method of the Data API, along with an open API client. We can verify that the operation was successful using the result of this method, which returns an IDataApiDataItemResult instance. This type contains both the data item that was saved and the status of the operation. The data item is obtained through the DataItem property and the status through the Status property.

After the data is passed to the Data API, it will be passed on to any attached wearables or other devices. Because we attached an instance of IDataApiDataListener to the Data API, the OnDataChanged() method of this listener will be invoked. The data has been received on another thread, so we have to make sure that when we update the UI, we do so on the UI thread. We can easily do this by using the RunOnUiThread() method.

Note

The OnDataChanged() method of the IDataApiDataListener instance is invoked on a background thread, thus all UI operations must be forwarded to the UI thread.

The OnDataChanged() method has one argument—a DataEventBuffer instance. This type inherits from IEnumerable<IDataEvent> with an additional Status property. We can iterate through the IDataEvent items and process them accordingly by taking advantage of LINQ.

Each IDataEvent item has a Type property, which is either DataEvent.TypeChanged or DataEvent.TypeDeleted. Since we only process data that has not been deleted, we will first check the Type property. Another property that is used to identify the data is the Uri property, which contains the path that was specified when the data was created. If there are multiple sources of data, which might be the case in some apps, we must first verify that it is the data we are expecting.

Once we have determined that we are going to process the item, we can read the associated data using the DataItem property. This property has a type of IDataItem, which contains the serialized DataMap instance.

As a result of using a DataMap type to synchronize data, we have to convert the IDataItem type back into DataMap. This is done using the DataMapItem type. This type has a static FromDataItem() method, which accepts an IDataItem instance and returns a DataMapItem instance. This type also has a Uri property that we could read, but, as we have already read the value from the IDataItem type, we can move on to the DataMap property.

The DataMap property returns the deserialized DataMap instance that was originally passed to the Data API from another device. We can now read the data map using the various get methods, such as GetString() or GetBoolean(). Each get method has two overloads: one that returns a system default, if no value is found for the specified key; and another that returns a provided default.

When our app starts, we want to be able to read any previously synchronized data. Instead of waiting for the data to be sent over the Data API, we can request the current data that uses the GetDataItemsAsync() method of the wearable Data API. The return type of this method is DataItemBuffer, which inherits from IEnumerable<IDataItem> with an additional Status property. We can use the Status property to determine if the request was successful, and then iterate through the result. Similar to items obtained from a DataEventBuffer instance, we read the Uri property and convert each item into a DataMap property. Then, we process the data map accordingly.

There's more...

In any case where we don't want to synchronize the data, but rather send it directly to another device, we can use the Message API instead of the Data API.

  1. We need to make sure that we implement the IMessageApiMessageListener interface on the activity:
    public class MainActivity :
    Activity, IMessageApiMessageListener {
      public void OnMessageReceived(IMessageEvent messageEvent) {
      }
    }
  2. Then, we add the listener instance to the Message API:
    WearableClass.MessageApi.AddListenerAsync(client, this);
  3. In order to communicate, we need a node to communicate with. We can use the GetConnectedNodesAsync() method to fetch all the connected nodes, making sure that the node is available for communication using the IsNearby property:
    await WearableClass.NodeApi.GetConnectedNodesAsync(client);
    var node = result.Nodes.FirstOrDefault(n => n.IsNearby);
  4. For sending data to another device, we pass the node ID, along with the path and data, to the SendMessageAsync() method:
    var bytes = BitConverter.GetBytes(count);
    await WearableClass.MessageApi.SendMessageAsync(
      client, node.Id, CounterPath, bytes);
  5. On another device, the OnMessageReceived() method will be invoked, and we can read the data using the GetData() method:
    public void OnMessageReceived(IMessageEvent messageEvent) {
      var bytes = messageEvent.GetData();
      if (bytes != null && bytes.Length> 0) {
        count = BitConverter.ToInt32(bytes, 0);
      }
      }
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset