Geeks With Blogs

News

Microsoft MVP


DZone MVB


Moderator at CodeASP.NET


Quiz Master







free counters
Free counters
Added on January 19,2012


Follow Me @vmsdurano

A bit About Me



Disclaimer
The opinions expressed herein are my own personal opinions and does not represent the opinions of my employers. Nor does it represent the opinion of my dog, because I don’t have one.


Vinz' Blog (ProudMonkey) "Code, Beer and Music ~ my way of being a programmer"

In my previous posts I've talked about setting up your development environment to get started with Android development using Xamarin and Visual Studio and also talked about a brief introduction about wearable. In this post I'm going to demonstrate how sync data in your android application.

Creating the Wear App Project

To get started let's fire up Visual Studio 2013 and select FILE > NEW > PROJECT. Under Templates > C# > Android, select Wear App (Android) Project. You should be able to see like this:

img1

Name your app to whatever you like and then click OK to let Visual Studio generate the necessary files for you. In this example I named it as "WearDemo". The image below shows the generated files with default sample codes to help you get started on building wear apps.

img2

Before we start modifying the default codes I'd like to point out that there are two ways to communicate between wearable and handheld device and these are the DataApi and the MessageApi. Below are the short description of each APIs:

DataApi exposes an API for components to read or write data items and assets. A DataItem provides data storage with automatic syncing between the handheld and wearable. Asset is used for sending blobs of data such as images. You attach assets to DataItems and the system automatically takes care of the transfer for you.

MessageApi exposes an API for components to send messages to other nodes. Messages should generally contain small payloads. You should use Assests with DataApi to store larger data.

In this particular demo, I'm going to use the DataApi to send/sync data between devices. Since DataApi is part of Google Play Services, then the first thing we need here is to add the following namespaces below:

using Android.Gms.Common.Apis;
using Android.Gms.Wearable;

Android.Gms.Common.Apis allow us to make use of GoogleApiClient which is the main entry point for Google Play Services integration. The Android.Gms.Wearable enable us to use the WearableClass. Next is to extend our MainActivity class to inherit the following interfaces:

IDataApiDataListener
IGoogleApiClientConnectionCallbacks
IGoogleApiClientOnConnectionFailedListener

IDataApiDataListener is used to receive data events. The IGoogleApiClientConnectionCallbacks provides callbacks that are called when the client is connected or disconnected from the service. IGoogleApiClientOnConnectionFailedListener provide callbacks for scenarios that result in a failed attempt to connect the client to the service.

Wrapping up together here's the sample code for sending data to the handheld device:

using System;
using Android.Runtime;
using Android.Widget;
using Android.OS;
using Android.Support.Wearable.Views;
using Java.Interop;
using Android.Gms.Common.Apis;
using Android.Gms.Wearable;
using System.Linq;

namespace WearDemo
{
    [Activity(Label = "WearDemo", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity,IDataApiDataListener, IGoogleApiClientConnectionCallbacks, IGoogleApiClientOnConnectionFailedListener
    {

        private IGoogleApiClient _client;
        const string _syncPath = "/WearDemo/Data";
        protected override void OnCreate(Bundle bundle) {
            base.OnCreate(bundle);

            _client = new GoogleApiClientBuilder(this, this, this)
                             .AddApi(WearableClass.Api)
                             .Build();

            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.Main);
            var v = FindViewById<WatchViewStub>(Resource.Id.watch_view_stub);
            v.LayoutInflated += delegate {

                // Get our button from the layout resource,
                // and attach an event to it
                Button button = FindViewById<Button>(Resource.Id.myButton);

                button.Click += delegate {
                    SendData();
                };
            };
        }

        public void SendData() {
            try {
                var request = PutDataMapRequest.Create(_syncPath);
                var map = request.DataMap;
                map.PutString("Message", "Vinz says Hello from Wearable!");
                map.PutLong("UpdatedAt", DateTime.UtcNow.Ticks);
                WearableClass.DataApi.PutDataItem(_client, request.AsPutDataRequest());
            }
            finally {
                _client.Disconnect();
            }

        }
        protected override void OnStart() {
            base.OnStart();
            _client.Connect();
        }
        public void OnConnected(Bundle p0) {
            WearableClass.DataApi.AddListener(_client, this);
        }

        public void OnConnectionSuspended(int reason) {
            Android.Util.Log.Error("GMS", "Connection suspended " + reason);
            WearableClass.DataApi.RemoveListener(_client, this);
        }

        public void OnConnectionFailed(Android.Gms.Common.ConnectionResult result) {
            Android.Util.Log.Error("GMS", "Connection failed " + result.ErrorCode);
        }

        protected override void OnStop() {
            base.OnStop();
            _client.Disconnect();
        }

        public void OnDataChanged(DataEventBuffer dataEvents) {
            var dataEvent = Enumerable.Range(0, dataEvents.Count)
                                      .Select(i => dataEvents.Get(i).JavaCast<IDataEvent>())
                                      .FirstOrDefault(x => x.Type == DataEvent.TypeChanged && x.DataItem.Uri.Path.Equals(_syncPath));
            if (dataEvent == null)
                return;

            //do stuffs here
        }
    }
}

Okay I'll try explain what happened in the code. At OnCreate event we build a Google Play Services client that includes the Wearable API. We then called the SendData() method at Button's click handler to send the data. The SendData() method contains the actual logic for sending the data. There we created a DataMapRequest by passing the path of the data object which is "/WearDemo/Data". The actual data is the DataMap which cotains a Message and UpdatedAt values. The receiving side can use the path to identify the origin of the data which I'll talk about later in this article.

The Events:

OnStart - connect to the data layer when the activity starts
OnConnected - triggers when the data layer connection is successful
OnStop - Disconnect from the data layer when the activity stops
OnConnectionSuspended and OnConnectionFailed - is where you do stuffs for required connection callbacks (for example in this demo we log errors and detach the service)
OnDataChanged - triggers when the data changes

Things to keep in mind:
* The path should always starts with forward-slash (/).
* Timestamps is a must when sending data because the OnDataChanged() event only gets called when the data really changes. Adding the Timestamp to the data will make sure that the method gets called.

Add this MetaData in the AndroidManifest.xml under <application> element:

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

Creating the Main App Project

In order to test the syncing/sending data scenario we need to create the main android app that will received the data object that is coming from the wearable. The main app will be installed in the handheld device (for example mobile or tablet).

Now right click on the solution project and select ADD > NEW PROJECT. In the Add New Project window select Visual C# > Android > Blank App (Android). You should be able to see like this:

img3

I named the project as "MainAppDemo" for simplicity. Just click OK to generate the necessary files for you. You should have something like this in your solution now:

img4

Before we start adding the logic to the main app. I'd like to highlight these below:

The Namespace of your Wear app and Main app should be the same. In this example the Wear app uses the namespace "WearDemo". So make sure to rename the namespace of your main app to "WearDemo" to match up. To change the default namespace you can follow this steps:

* Go to PROJECT > PROPERTIES > DEFAULT NAMESPACE
* To change the rest you can use CTRL + H and replace the default namespace to "WearDemo"
* You can also use the refactor code to change the namespace. To do this just simply right click on the namespace and select REFACTOR > RENAME

The package name of your Wear and Main app should also be the same. You can find the package name by right clicking on the project and select PROPERTIES > ANDROID MANIFEST as shown in the image below:

img5

If you are following this example then make sure that both package name is set to "WearDemo.WearDemo". Make sure to build both projects to see if it builds successfully. Once you've done that then let's go ahead and start modifying the project. First change the value of "Compile using Android version" to "API Level 21 (Xamarin.Android v5.0 Support). See image below:

img7

Under references check if you have Xamarin.Android.Support.V4. If you don't have that then just right click on the References and select MANAGE NUGET PACKAGES. Under Online > Nuget.Org search "Xamarin.Android.Support.V4". You should be able to see something like this:

img6

Just click install and wait until it is done. Now do the same steps and install "Xamarin.Android.Wear -Version 1.0.0".

Adding the WearableListenerService

Extending the WearableListenerService lets you listen for any updates in the data layer. The system manages the lifecycle of the service, binding to the service when it needs to send data items or messages and unbinding the service when no work is needed.

Having that statement we are going to use the WearableListenerService to listen for an update from the data layer and handle the data. So the next step is to add a class that extends WearableListenerService. To do this right click on the project root and select ADD > Class and name it as "WearService". Here's the whole logic of the class:

using System.Linq;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Gms.Wearable;
using Android.Gms.Common.Apis;
using Android.Support.V4.Content;

namespace WearDemo
{
    [Service]
    [IntentFilter(new[] { "com.google.android.gms.wearable.BIND_LISTENER" })]
    public class WearService : WearableListenerService
    {
        const string _syncPath = "/WearDemo/Data";
        IGoogleApiClient _client;

        public override void OnCreate() {
            base.OnCreate();
            _client = new GoogleApiClientBuilder(this.ApplicationContext)
                    .AddApi(WearableClass.Api)
                    .Build();

            _client.Connect();

            Android.Util.Log.Info("WearIntegration", "Created");
        }

        public override void OnDataChanged(DataEventBuffer dataEvents) {
            var dataEvent = Enumerable.Range(0, dataEvents.Count)
                                      .Select(i => dataEvents.Get(i).JavaCast<IDataEvent>())
                                      .FirstOrDefault(x => x.Type == DataEvent.TypeChanged && x.DataItem.Uri.Path.Equals(_syncPath));
            if (dataEvent == null)
                return;

            //get data from wearable
            var dataMapItem = DataMapItem.FromDataItem(dataEvent.DataItem);
            var map = dataMapItem.DataMap;
            string message = dataMapItem.DataMap.GetString("Message");

            Intent intent = new Intent();
            intent.SetAction(Intent.ActionSend);
            intent.PutExtra("WearMessage", message);
            LocalBroadcastManager.GetInstance(this).SendBroadcast(intent);
        }
    }
}

The code above implements OnDataChanged event which filters incoming data event for those of "TypeChanged", checks for the data object path of "/WearDemo/Data", then broadcast the data locally.

The Main Activity

Here’s the code block for our main activity class:

using Android.App;
using Android.Content;
using Android.Widget;
using Android.OS;
using Android.Support.V4.Content;

namespace WearDemo
{
    [Activity(Label = "MainAppDemo", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity
    {
        TextView _txtMsg;

        protected override void OnCreate(Bundle bundle) {
            base.OnCreate(bundle);

            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.Main);

            // Get our TextBox from the layout resource,
            _txtMsg = FindViewById<TextView>(Resource.Id.txtMessage);


            IntentFilter filter = new IntentFilter(Intent.ActionSend);
            MessageReciever receiver = new MessageReciever(this);
            LocalBroadcastManager.GetInstance(this).RegisterReceiver(receiver, filter);
        }

        public void ProcessMessage(Intent intent) {
            _txtMsg.Text = intent.GetStringExtra("WearMessage");
        }

        internal class MessageReciever : BroadcastReceiver
        {
            MainActivity _main;
            public MessageReciever(MainActivity owner) { this._main = owner; }
            public override void OnReceive(Context context, Intent intent) {
                _main.ProcessMessage(intent);
            }
        }
    }
}

In the code above, we register to receive broadcast from the ListenerService at OnCreate() event and then define a nested class that extends the BroadcastReceiver, implements the OnReceive() method, and extract the data. The Process() method handles the displaying of the data to the UI.

The Main layout

Change your Main.axml to this:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <TextView
        android:id="@+id/txtMessage"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:gravity="center"
        android:textColor="@android:color/white"
        android:textSize="80sp" />
</LinearLayout>

Adding the MetaData for Google Play Services

And finally, Add the meta data in the AndroidManifest.xml under <application> element:

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

That's it. In the next post I'm going to demonstrate how to deploy each application in different devices and test the result. I hope someone find this post useful Smile

Posted on Thursday, April 23, 2015 1:15 AM C# , .NET , Xamarin , Visual Studio | Back to top


Comments on this post: Xamarin.Android - Syncing data between Wearable and Handheld devices

comments powered by Disqus

Copyright © Vincent Maverick Durano | Powered by: GeeksWithBlogs.net