Back in November I blogged about making my app usable on an android wear watch. At the time this was a
prototype.
I have now got it working nicely with communication features from the phone to the watch, so you can select exercises on your phone and then push the selection to the watch.
All you need to do is have the application currently running and open on your phone and watch and make your selection.
To do this, I had to write some android code using Android Studio and the wearable component of google play services.
Messaging between the phone and the watch is covered in the google development documentation
here. These
examples are
both also very useful.
Selecting Exercises on the Phone
Choosing to push selection to Watch
Previewing on Phone
Using a bit of trial and error and the
blog post here as well documentation on
building plugins I managed to get it working.
One class is used to send and receive messages for google play services and start the listener for messages on the watch.
package org.nativescript.androidwear.messaging;
import android.content.Context;
import android.widget.Toast;
import android.os.Looper;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.wearable.MessageApi;
import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;
import com.google.android.gms.wearable.WearableListenerService;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class SendReceiveMessage extends WearableListenerService {
private static final long CONNECTION_TIME_OUT_MS = 100;
private static SendReceiveMessageListener onMessageReceivedCallback = null;
private static GoogleApiClient client;
private static SendReceiveMessage listener;
private static boolean notifyToast = false;
@Override
public void onMessageReceived(MessageEvent messageEvent) {
receive(this, messageEvent.getPath(), new String(messageEvent.getData()));
}
public static void registerListener(SendReceiveMessageListener listener)
{
onMessageReceivedCallback = listener;
}
/**
* Receive message, displaying as a toast if no action set, otherwise running the action
*/
public static void receive(Context context, String messagePath, String messageReceived) {
if (onMessageReceivedCallback == null) {
if (notifyToast) {
Toast.makeText(context, messagePath + " received no callback", Toast.LENGTH_LONG).show();
}
}
else
{
if (notifyToast) {
Toast.makeText(context, messagePath + " received with callback", Toast.LENGTH_LONG).show();
}
onMessageReceivedCallback.receive(messagePath,messageReceived);
}
}
/**
* Start listening for messages
*/
public static void startListener(Context context, boolean notify)
{
// Get Client
client = new GoogleApiClient.Builder(context)
.addApi(Wearable.API)
.build();
listener = new SendReceiveMessage();
Wearable.MessageApi.addListener(client,listener);
notifyToast = notify;
if (notify) {
Toast.makeText(context, "Listener started!!", Toast.LENGTH_LONG).show();
}
}
/**
* Stop listening for messages
*/
public static void stopListener(Context context, boolean notify)
{
Wearable.MessageApi.removeListener(client,listener);
if (notify) {
Toast.makeText(context, "Listener stopped!!", Toast.LENGTH_LONG).show();
}
}
/**
* Sends a message to the connected mobile device, telling it to show a Toast.
*/
public static void send(final Context context, final String messagePath, final String messageToSend, final boolean notify) {
// Get Client
final GoogleApiClient client = new GoogleApiClient.Builder(context)
.addApi(Wearable.API)
.build();
new Thread(new Runnable() {
@Override
public void run() {
// Get First Node
client.blockingConnect(CONNECTION_TIME_OUT_MS, TimeUnit.MILLISECONDS);
NodeApi.GetConnectedNodesResult result =
Wearable.NodeApi.getConnectedNodes(client).await();
List<Node> nodes = result.getNodes();
if (nodes.size() > 0) {
final String nodeId = nodes.get(0).getId();
// Send Message
new Thread(new Runnable() {
@Override
public void run() {
client.blockingConnect(CONNECTION_TIME_OUT_MS, TimeUnit.MILLISECONDS);
Wearable.MessageApi.sendMessage(client, nodeId, messagePath, messageToSend.getBytes()).setResultCallback(new ResultCallback<MessageApi.SendMessageResult>() {
@Override
public void onResult(MessageApi.SendMessageResult sendMessageResult) {
if (!sendMessageResult.getStatus().isSuccess()) {
Looper.prepare();
Toast.makeText(context, "Message not sent - failure", Toast.LENGTH_LONG).show();
Looper.loop();
}
}
});
client.disconnect();
if (notify) {
Looper.prepare();
Toast.makeText(context, "Message sent", Toast.LENGTH_LONG).show();
Looper.loop();
}
}
}).start();
}
else
{
if (notify) {
Looper.prepare();
Toast.makeText(context, "Message not sent - no nodes", Toast.LENGTH_LONG).show();
Looper.loop();
}
}
client.disconnect();
}
}).start();
}
}
The second file is an interface, used as a bridging mechanism between the android code and nativescript.
package org.nativescript.androidwear.messaging;
/**
* Created by Peter on 1/01/2016.
*/
public interface SendReceiveMessageListener {
void receive(final String messagePath, final String messageReceived);
}
The build.gradle file is set up to build a library file.
apply plugin: 'com.android.library'
android {
compileSdkVersion 23
buildToolsVersion "21.1.2"
defaultConfig {
minSdkVersion 17
targetSdkVersion 22
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.google.android.gms:play-services-wearable:8.4.0'
}
Once built you can use the library aar file in plugin.
nativescript-wear-messaging
--platforms
----android
------libs
--------wearmessaging.aar
-------AndroidManifest.xml
-------include.gradle
--index.js
-- package.json
The typescript version of the index.js file is
var app = require('application');
var context = app.android.context;
// If notify is turned on, it will display debug toast messages for testing
var notify = false;
// Call this first in the page you want to use the messaging in
export function startListener() {
try {
org.nativescript.androidwear.messaging.SendReceiveMessage.startListener(context, notify);
}
catch (ex) {
alert(ex.message);
}
}
// Call this when the page is closed
export function stopListener() {
try {
org.nativescript.androidwear.messaging.SendReceiveMessage.stopListener(context, notify);
}
catch (ex) {
alert(ex.message);
}
}
// Pass in a callback in the page you want to listen for messages, you can JSON.parse to convert strings back into objects
export function receive(receiveCallback: (messagePath: string, messageReceived: string) => void) {
try {
var sendReceiveMessageListener = new org.nativescript.androidwear.messaging.SendReceiveMessageListener({ receive: receiveCallback });
org.nativescript.androidwear.messaging.SendReceiveMessage.registerListener(sendReceiveMessageListener);
}
catch (ex) {
alert(ex.message);
}
}
// Call this from your phone app, the message path is the keyname of the message, you can use JSON.stringify to convert objects to be sent
export function send(messagePath: string, messageToSend: string) {
try {
org.nativescript.androidwear.messaging.SendReceiveMessage.send(context, messagePath, messageToSend, notify);
}
catch (ex) {
alert(ex.message);
}
}
The manifest file needs to contain a reference to the custom WearListenerService. This will then be copied into the overall AndroidManifest file.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0" >
<application>
<service
android:name="org.nativescript.androidwear.messaging.SendReceiveMessage" >
<intent-filter>
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
</intent-filter>
</service>
</application>
</manifest>
The include gradle needs to include detail on the dependencies
//default elements
android {
productFlavors {
"nativescript-wear-messaging" {
dimension "nativescript-wear-messaging"
}
}
}
//optional elements
dependencies {
compile 'com.android.support:appcompat-v7:23.1.1'
compile "com.google.android.gms:play-services-wearable:8.4.0"
}
The plugin needs to be installed on the mobile phone and watch versions of the code. Messages can be sent to/from each device. A snippet of the code for the model can be seen below.
// Called on page load
public startListenerReload() {
try {
// Start listening for messages
wearMessaging.startListener();
// When message is received process then
wearMessaging.receive(
(messagePath: string, messageReceived: string) => {
try {
// Check message is the one we are interested in, store it in application settings
// Convert to JSON object and display on screen
if (messagePath == this.messagingPath) {
appSettings.setString("exercises", messageReceived);
var exercises = <IExercises>JSON.parse(messageReceived);
this.loadData(exercises);
}
}
catch (ex) {
alert(ex.message);
}
}
);
// load previously loaded messages
var messageReceived = appSettings.getString("exercises", "");
if (messageReceived != "") {
var exercises = <IExercises>JSON.parse(messageReceived);
this.loadData(exercises);
}
}
catch (ex) {
alert(ex.message);
}
}
// Send message to other device
public sendMessages(exercises: IExercises) {
wearMessaging.send(this.messagingPath, JSON.stringify(exercises));
}