Android Wear. Shared storage for smartwatch and the phone

Danylo Volokh
Android Development by Danylo :)
6 min readFeb 25, 2018

Actually there is no “storage” for smartwatch and the phone. But there are some Wear API’s which can be used for this purpose.

All the data was taken from this web page from Android Developers

I will show a one direction data transfer: from handhold to the smartwatch

But he same techniques can be used to transfer data: from smartwatch to the handhold

So this article is about:

  1. How to save data using Wear API’s
  2. How to get saved data on the watch
  3. How to listen to the data changes on the wear
  4. How to remove data from the storage

How to save data using Wear API’s

We need to understand that all this is happening asynchronously over the cloud. Device which is saving the data does it with implicitly specified “node id”. This “node id” is the unique identifier of the device that stores the data.

Technically a single smartwatch can access data from multiple nodes which basically means multiple phones(tablets)

On you phone (tablet):

  1. Create a PutDataMapRequest object
  2. Get map object from it and put data to it. Here is the list of methods we can use to put data:
public void putBoolean(String key, boolean value)
public void putByte(String key, byte value)
public void putInt(String key, int value)
public void putLong(String key, long value)
public void putFloat(String key, float value)
public void putDouble(String key, double value)
public void putString(String key, String value)
public void putAsset(String key, Asset value)
public void putDataMap(String key, DataMap value)
public void putByteArray(String key, byte[] value)
public void putLongArray(String key, long[] value)
public void putFloatArray(String key, float[] value)
public void putStringArray(String key, String[] value)
public void putStringArrayList(String key, ArrayList<String> value)
public void putDataMapArrayList(String key, ArrayList<DataMap> value)
public void putIntegerArrayList(String key, ArrayList<Integer> value)

3. Create a PutDataRequest from PutDataMapRequest to send the data into “Wearable” storage(the could node) by using DataClient

private val TAG = "TAG"
private val pathToYourData = "/path-to-your-data"
private val KEY_NAME = "KEY_NAME"
/**
* We need to fetch DataClient to work with a PutDataMapRequest
*/
private val mDataClient = Wearable.getDataClient(context)
/**
* Create a putDataMapRequest
*/
val putDataMapReq = PutDataMapRequest.create(pathToYourData)
/**
* Get the DataMap object from the request
*/
val dataMap = putDataMapReq.dataMap
/**
* Add the data to the map
*/
dataMap.putString(KEY_NAME, "Danylo")
/**
* Get the putDataRequest
*/
var putDataReq = putDataMapReq.asPutDataRequest()
/**
* Put DataItem into the mDataClient object
*/
val putDataTask = mDataClient.putDataItem(putDataReq)
/**
* Here is a very important moment. The last step can be done in
* two ways
*
* - synchronous
* - asynchronous

*/
// synchronous
val result = Tasks.await(putDataTask)
// OR// asynchronous
putDataTask.addOnCompleteListener({
Log.v(TAG, "saveWearableData complete listener
})

Synchronous has to be done in separate thread from the main:

val result = Tasks.await(putDataTask)

Asynchronous can be done on the main thread and after it’s done the complete callback will be invoked

putDataTask.addOnCompleteListener({
Log.v(TAG, "saveWearableData complete listener
})

If a delay in syncing would negatively impact user experience, call setUrgent().

When creating a putDataRequest object:

var putDataReq = putDataMapReq
.asPutDataRequest()
.setUrgent()

How to get saved data on the watch

On you smartwatch:

You can do this in your Activity, Service or anywhere. You just need to have context. You have to make it in few steps:

  1. Get the “node id” which was used to store the data. This “node id” represents the device which stored this data. We can get a list of nodes (devices) which are synchronized with our smartwatch. This device not necessarily has to be connected right now (devices can be temporarily disconnected) you will still get this device and the node it created.
  2. Use this “node id” to get the data which was stored on that node.
  3. Find your data using path. The same path we used when we stored the data.

This is the synchronous way so it has to be done is a separate thread (not the UI)

private val KEY_NAME = "KEY_NAME"var nodeId: String? = null
/**
* Get the node id first
*/
val nodesTask = Wearable.getNodeClient(this).connectedNodes/**
* Execute the task synchronously
*/
val results = Tasks.await(nodesTask)/**
* You can choose your own criteria to get necessary node
*
* Here we iterate over all the nodes and :
* - Skip devices that are not "nearby".
* - Choose first connected node and then "break" from the loop
*/
for (node in results) {

Log.v(TAG, "getNodeIdWithData, node.isNearby ${node.isNearby}")

if (!node.isNearby) {
continue
}

Log.v(TAG, "getNodeIdWithData, node.isNearby ${node.isNearby}")
/**
* Get the id from the node
*/
nodeId = node.id /**
* We handle first data that we found
*/

break
}

val pathToYourData = "/path-to-your-data"
/**
* Create the uri which we use to access the "node" data
*/
val uri = Uri.Builder()
.scheme(PutDataRequest.WEAR_URI_SCHEME)
.path(pathToYourData)
.authority(nodeId)
.build()

val dataItem = Wearable.getDataClient(this).getDataItem(uri)
val result = Tasks.await(dataItem)
/**
* Here we need to check the result for null. I've noticed that
* sometimes it can be null.
*/
if(result != null){ /**
* Get the map we created during storing the data.
*/
val dataMap = DataMapItem.fromDataItem(result).dataMap
val name = dataMap.getString(KEY_NAME)
// name = "Danylo"
}

If you don’t want to run a separate thread you can wait for Task completion with OnCompleteListener

// Instead of synchronous val result = Tasks.await(task)// asynchronous        
task.addOnCompleteListener({
Log.v(TAG, "complete listener
})

How to listen to the data changes on the wear

On you smartwatch:

This can be done 2 ways

  • Locally in the Activity
  • In the Service

Here is how it’s done in the activity:

private val KEY_NAME = "KEY_NAME"
private val pathToYourData = "/path-to-your-data"
/**
* Subscribe the listener on in onResume
*/
override fun onResume() {
super.onResume()
Log.v(TAG, "onResume")
Wearable.getDataClient(this).addListener(this)
}
/**
* Unsubscribe the listener on in onPause
*/
override fun onPause() {
super.onPause()
Log.v(TAG, "onPause")
Wearable.getDataClient(this).removeListener(this)
}
/**
* This method will be called when changes in the data will be done
*/
override fun onDataChanged(dataBuffer: DataEventBuffer) {
Log.v(TAG, ">> onDataChanged, dataBuffer " + dataBuffer)

for (event in dataBuffer) {
Log.v(TAG, "onDataChanged, event getType" + event.getType())

when(event.type){
/**
* We get this event type it data was created or
* changed
*/
DataEvent.TYPE_CHANGED ->{
val item = event.dataItem
Log.v(TAG, "onDataChanged, item " + item)

val uri = item.uri
val path = uri.path
/**
* We compare the path to find our path
*/
if (path.compareTo(pathToYourData) == 0) {
/**
* "authority" from the uri the the NodeID
*/
nodeId = item.uri.authority val dataItem = DataMapItem.fromDataItem(item)
val dataMap = dataItem.dataMap

val name = dataMap.getString(KEY_NAME)
}
}
/**
* We get this event type it data was created or
* changed
*/
DataEvent.TYPE_DELETED -> {
Log.v(TAG, "onDataChanged, item deleted")
// handle data deletion hear
}
}
}
Log.v(TAG, "<< onDataChanged, dataBuffer " + dataBuffer)
}

In order to continuously listen to the changes in the service you need to implement WearableListenerService

And implement the same method onDataChanged as we did in Activity:

override fun onDataChanged(dataEvents: DataEventBuffer) {// handle data changes here. Store the data locally or do anything // you need.}

Define your service in AndroidManifest.xml of wear application

<service android:name=".service.NameOfYourService" ><intent-filter>   <action 
android:name="com.google.android.gms.wearable.DATA_CHANGED"/>
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/path-to-your-data" />
</intent-filter></service>

This way you will get all the “data update” and “data deleted” events in the service. Use it to listen to the changes in background.

How to remove data from the storage

On you phone(tablet):

private val pathToYourData = "/path-to-your-data"val uri = Uri.Builder()
.scheme(PutDataRequest.WEAR_URI_SCHEME)
.path(pathToYourData)
/**
* wildcard means delete path from all the nodes
*/
.authority("*")
.build()

Wearable.getDataClient(context).deleteDataItems(uri)

After this on your smartwatch you will get a data change event with type DataEvent.TYPE_DELETED

And that’s it.

Cheers ;)

--

--