Android Wear: How to send data from Android Wear to Mobile Device ?

Android Wear or Let say Smart Watches are new and trending technology. Android apps are updating and they all comes with Android wear support. if you want it is in your app you can find simple Helloworld wear app tutorials easily on Google. but what if you want to explore it more let say you want to display a List view in android wear or get some kind of input from android wear app. so these tutorial is for you. In this tutorial i have created simple list in android wear app in which user click on list item and an app in mobile device gets open. for example you want to display list of songs in android wear app and on the click of that you want to change song in your device or let say you can display to do list on android wear and on the click of list item you can open to do item details inside your mobile app. this list can be infinite and its totally depends on you and apps in android.

Android Wear sample


Steps

Step - 1
First of let i start with adding a android wear module in development. If you have already added android wear module in you app you can skip this step. To add right click on your app root folder (name of your app folder) and go to New > Module. Select Android Wear Module and click on next. Give same name in Application name (Mobile app name) and in Module Name write wear. so you can differentiate them. Make sure you give same package name also. because when you send some data through mobile app or wear app MessageApi will check for the same package and pass that data. so if you give different name you won't be able to send or receive data. On the next screen select Blank Wear Activity and on the next screen give proper name to it.

Note - Above steps are for Android studio wizard. Sorry for eclipse developers (Please migrate to AS)

Step - 2
So our android wear module is added. I hope you have no issue in above steps. in the main activity or launcher activity i have created a List (basically list of my tutorials). For learning purpose you can start with same and after that you can change it is as you want. so put below code in appropriate layout files.

wear/layout/activity_list.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.wearable.view.WatchViewStub 
   xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/watch_view_stub"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#e4e5e5"
    app:rectLayout="@layout/rect_activity_list"
    app:roundLayout="@layout/round_activity_list"
    tools:context=".ListActivity"
    tools:deviceIds="wear">

</android.support.wearable.view.WatchViewStub>



wear/layout/rect_activity_list.xml & wear/layout/round_activity_list.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.wearable.view.BoxInsetLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context=".ListActivity"
    tools:deviceIds="wear_round">

        <android.support.wearable.view.WearableListView
            android:id="@+id/listView1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="5dp"
            android:overScrollMode="never" />

        <TextView
            android:id="@+id/tv_header"
            android:layout_width="wrap_content"
            android:layout_height="40dp"
            android:layout_gravity="top|center"
            android:textSize="30sp"
            android:layout_marginTop="10dp"
            android:textColor="#52bf90"
            android:fontFamily="sans-serif-light"
            android:text="@string/app_name"/>

</android.support.wearable.view.BoxInsetLayout>




Step - 3
List layout is done in Step -2. now let's jump to coding part where i have initialize WearableListView with Array adapter. Using GoogleApiClient i have checked if there is a connection to a android wear from an app or not. we can get all connected nodes or devices using Wearable.NodeApi.getConnectedNodes method. check below code and make sure you understand or go through it for one time so you know what is actually going on. otherwise you will get confused.

wear/java/ListActivity.java

package com.androprogrammer.tutorials;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.wearable.view.WatchViewStub;
import android.support.wearable.view.WearableListView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
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.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;

import java.util.ArrayList;

public class ListActivity extends Activity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener,WearableListView.ClickListener {

    private WearableListView mListView;

    private ArrayList<String> listItems;

    Node mNode; // the connected device to send the message to
    GoogleApiClient mGoogleApiClient;
    private boolean mResolvingError=false;

    public static String SERVICE_CALLED_WEAR = "WearListClicked";
    public static String TAG = "WearListActivity";


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_list);

        //Connect the GoogleApiClient
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addApi(Wearable.API)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .build();

        initializeListItems();
        final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
        stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
            @Override
            public void onLayoutInflated(WatchViewStub stub) {
                mListView = (WearableListView) stub.findViewById(R.id.listView1);

                mListView.setAdapter(new MyAdapter(ListActivity.this, listItems));
                mListView.setClickListener(ListActivity.this);

            }
        });
    }

    private void initializeListItems() {

        listItems = new ArrayList<>();
        listItems.add("Async File Read");
        listItems.add("Battery Status");
        listItems.add("Volume Setting");
        listItems.add("Frame Animation");
        listItems.add("Video Player");
        listItems.add("Circular Image View");
        listItems.add("Track User Location");
        listItems.add("Take Image");
        listItems.add("Image Grid View");
        listItems.add("Image Switcher");
        listItems.add("Tabs with Toolbar");
        listItems.add("Icon Tabs with Toolbar");
        listItems.add("Push Notification");


    }

    @Override
    protected void onStart() {
        super.onStart();
        if (!mResolvingError) {
            mGoogleApiClient.connect();
        }
    }

    /**
     * Resolve the node = the connected device to send the message to
     */
    private void resolveNode() {

        Wearable.NodeApi.getConnectedNodes(mGoogleApiClient)
                .setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() {
                    @Override
                    public void onResult(NodeApi.GetConnectedNodesResult nodes) {
                        for (Node node : nodes.getNodes()) {
                    mNode = node;
                }
            }
        });
    }

    @Override
    public void onConnected(Bundle bundle) {
        resolveNode();
    }

    @Override
    public void onConnectionSuspended(int i) {

    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {

    }

    /**
     * Send message to mobile handheld
     */
    private void sendMessage(String Key) {

        if (mNode != null && mGoogleApiClient!= null && mGoogleApiClient.isConnected()) {
            Log.d(TAG, "-- " + mGoogleApiClient.isConnected());
            Wearable.MessageApi.sendMessage(
                    mGoogleApiClient, mNode.getId(), SERVICE_CALLED_WEAR + "--" + Key, null).setResultCallback(

                    new ResultCallback<MessageApi.SendMessageResult>() {
                        @Override
                        public void onResult(MessageApi.SendMessageResult sendMessageResult) {

                            if (!sendMessageResult.getStatus().isSuccess()) {
                                Log.e(TAG, "Failed to send message with status code: "
                                        + sendMessageResult.getStatus().getStatusCode());
                            }
                        }
                    }
            );
        }

    }

    @Override
    public void onClick(WearableListView.ViewHolder viewHolder) {
        TextView view = (TextView) viewHolder.itemView.findViewById(R.id.row_tv_name);
        String Key = view.getText().toString();
        Log.d(TAG, Key);
        sendMessage(Key);
    }

    @Override
    public void onTopEmptyRegionClick() {
        Toast.makeText(this, "You tapped on Top empty area", Toast.LENGTH_SHORT).show();
    }


    private class MyAdapter extends WearableListView.Adapter {
        private final LayoutInflater mInflater;
        private ArrayList<String> data;

        private MyAdapter(Context context, ArrayList<String> listItems) {
            mInflater = LayoutInflater.from(context);
            data = listItems;
        }

        @Override
        public WearableListView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new WearableListView.ViewHolder(
                    mInflater.inflate(R.layout.row_wear_list, null));
        }

        @Override
        public void onBindViewHolder(WearableListView.ViewHolder holder, int position) {
            TextView view = (TextView) holder.itemView.findViewById(R.id.row_tv_name);
            view.setText(data.get(position));
            holder.itemView.setTag(position);
        }

        @Override
        public int getItemCount() {
            return data.size();
        }
    }
}



wear/layout/row_wear_list.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="80dp"
    android:orientation="horizontal"
    android:padding="8dp"
    android:gravity="center">

    <TextView
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:text="Medium Text"
        android:textStyle="bold"
        android:ellipsize="end"
        android:maxEms="15"
        android:padding="10dp"
        android:ems="15"
        android:background="@android:color/white"
        android:textColor="@android:color/black"
        android:fontFamily="sans-serif-condensed-light"
        android:gravity="center"
        android:layout_height="match_parent"
        android:layout_width="wrap_content"
        android:textSize="16sp"
        android:id="@+id/row_tv_name" />

</LinearLayout>


wear/AndroidManifest.xml
<meta-data android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />


Please add above lines of code in manifest file of wear module. because we are using google play service to communicate. otherwise it will give error.

Step - 4
Now android wear module is ready. you can run it separately on emulator and check it. now we require a bridge which can pass android wear message to android mobile app. so when user click on list item in android wear a service get some sort of message and on the basis of that message we start appropriate activity. Using WearableListenerService we will create service which will do same for our app.

mobile/java/WearListCallListenerService.java

package com.androprogrammer.tutorials.services;

import android.content.Intent;
import android.util.Log;

import com.androprogrammer.tutorials.activities.Listactivity;
import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.WearableListenerService;

/**
 * Created by Wasim on 08-05-2015.
 */
public class WearListCallListenerService extends WearableListenerService {

    public static String SERVICE_CALLED_WEAR = "WearListClicked";


    @Override
    public void onMessageReceived(MessageEvent messageEvent) {
        super.onMessageReceived(messageEvent);

        String event = messageEvent.getPath();

        Log.d("Listclicked", event);

        String [] message = event.split("--");

        if (message[0].equals(SERVICE_CALLED_WEAR)) {

            startActivity(new Intent((Intent) Listactivity.getInstance().tutorials.get(message[1]))
                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
        }
    }
}



Step - 5
In Mobile app put below code to register your service.

mobile/AndroidManifest.xml

<service android:name=".services.WearListCallListenerService">
            <intent-filter>
                <action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
            </intent-filter>
        </service>


That's it...
Now run wear module in android wear emulator and mobile module in device. make sure you are connected. I hope you understand the full tutorial. you can get full source code from my Github repo. if you find it helpful please share this tutorial.

Keep coding...

3 comments :

  1. Thanks. This tutorial is very helpful and excellent. :)

    ReplyDelete
  2. i am not able to see the list view item why?

    ReplyDelete
    Replies
    1. Can you check and let me know what error you are getting ?

      Delete