Hi there,
As part of some training, I've been asked to create a cross platform (iOS and Android) app using Xamarin.Forms. I'm very new to Xamarin and mobile development generally, but for some reason, I thought it would be a good idea to also add a Wear OS component.
So far, I've managed to make a functioning Xamarin.Forms app and the Wear OS counterpart, but I haven't had any luck sending data from the watch to the phone. Below is what seems to be the relevant code. Be warned that it is... creative. I've been following a guide on codeproject (that I can't link because I'm new...) and just kind of hacking it into what I've got. From what I can see the watch is sending data but the phone just isn't picking it up. Any help would be greatly appreciated and I can send any code you want to look at. Thanks!
WearOS MainActivity:
using System;
using Android.Runtime;
using Android.App;
using Android.Widget;
using Android.OS;
using Android.Support.Wearable.Views;
using Java.Lang;
using System.Linq;
using Android.Support.Wearable.Activity;
using ScoreKeeper.Models;
using Android.Gms.Common.Apis;
using Android.Gms.Wearable;
using Export = Java.Interop.ExportAttribute;
using System.IO;
using Newtonsoft.Json;
using static Android.Gms.Common.Apis.GoogleApiClient;
using Android.Gms.Common;
namespace ScoreKeeper.WearOS
{
[Activity(Label = "@string/app_name", MainLauncher = true)]
public class MainActivity : WearableActivity, IDataApiDataListener, IConnectionCallbacks, IOnConnectionFailedListener
{
public Button T1IncScore { get; set; }
public Button T1DecScore { get; set; }
public Button T2IncScore { get; set; }
public Button T2DecScore { get; set; }
public Button EndButton { get; set;}
public Button SyncButton { get; set; }
public TextView T1Score { get; set; }
public TextView T2Score { get; set; }
private GoogleApiClient _client;
const string _syncPath = "/ScoreKeeper/Data";
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.activity_main);
T1IncScore = FindViewById<Button>(Resource.Id.t1IncScore);
T1IncScore.Click += delegate
{
T1IncScore_Clicked();
};
T1DecScore = FindViewById<Button>(Resource.Id.t1DecScore);
T1DecScore.Click += delegate
{
T1DecScore_Clicked();
};
T2IncScore = FindViewById<Button>(Resource.Id.t2IncScore);
T2IncScore.Click += delegate
{
T2IncScore_Clicked();
};
T2DecScore = FindViewById<Button>(Resource.Id.t2DecScore);
T2DecScore.Click += delegate
{
T2DecScore_Clicked();
};
EndButton = FindViewById<Button>(Resource.Id.endButton);
EndButton.Click += delegate
{
End_Clicked();
};
SyncButton = FindViewById<Button>(Resource.Id.syncButton);
SyncButton.Click += delegate
{
Sync_Clicked();
};
T1Score = FindViewById<TextView>(Resource.Id.t1Score);
T2Score = FindViewById<TextView>(Resource.Id.t2Score);
_client = new Builder(this, this, this)
.AddApi(WearableClass.API)
.Build();
SetAmbientEnabled();
}
[@Export("T1IncScore_Clicked")]
private void T1IncScore_Clicked()
{
int n = int.Parse(T1Score.Text);
n++;
T1Score.Text = n.ToString();
}
[@Export("T2IncScore_Clicked")]
private void T2IncScore_Clicked()
{
int n = int.Parse(T2Score.Text);
n++;
T2Score.Text = n.ToString();
}
[@Export("T1DecScore_Clicked")]
private void T1DecScore_Clicked()
{
int n = int.Parse(T1Score.Text);
if (n > 0)
{
n--;
T1Score.Text = n.ToString();
}
}
[@Export("T2DecScore_Clicked")]
private void T2DecScore_Clicked()
{
int n = int.Parse(T2Score.Text);
if (n > 0)
{
n--;
T2Score.Text = n.ToString();
}
}
[@Export("End_Clicked")]
private void End_Clicked()
{
Score score = new Score(int.Parse(T1Score.Text), int.Parse(T2Score.Text));
if(Save(score))
{
Toast.MakeText(Application.Context, string.Concat("Saved Score: ", score.Result), ToastLength.Long).Show();
T1Score.Text = "0";
T2Score.Text = "0";
}
}
[@Export("Sync_Clicked")]
private void Sync_Clicked()
{
Toast.MakeText(Application.Context, "Syncing..." , ToastLength.Short).Show();
string file = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "watchScores.json");
SendData(file);
File.Delete(file);
Toast.MakeText(Application.Context, "Synced", ToastLength.Long).Show();
}
public bool Save(Score score)
{
try
{
string file = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "watchScores.json");
string s = JsonConvert.SerializeObject(score);
File.AppendAllText(file, s + "\n");
Console.WriteLine(s);
return true;
}
catch (System.Exception e)
{
Console.WriteLine(e.Message);
return false;
}
}
public void SendData(string file)
{
try
{
if (File.Exists(file))
{
string fileContents = File.ReadAllText(file);
var request = PutDataMapRequest.Create(_syncPath);
var map = request.DataMap;
map.PutString("Message", fileContents);
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 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;
}
}
public void OnConnected(Bundle connectionHint)
{
WearableClass.DataApi.AddListener(_client, this);
}
public void OnConnectionSuspended(int cause)
{
Android.Util.Log.Error("GMS", "Connection suspended " + cause);
WearableClass.DataApi.RemoveListener(_client, this);
}
public void OnConnectionFailed(ConnectionResult result)
{
Android.Util.Log.Error("GMS", "Connection failed " + result.ErrorCode);
}
protected override void OnStop()
{
base.OnStop();
_client.Disconnect();
}
}
}
WearService (Android phone)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.Gms.Common.Apis;
using Android.Gms.Wearable;
using Android.OS;
using Android.Runtime;
using Android.Support.V4.Content;
using Android.Views;
using Android.Widget;
using ScoreKeeper.Views;
using Xamarin.Forms;
[assembly: Dependency(typeof(ScoreKeeper.Droid.Services.WearService))]
namespace ScoreKeeper.Droid.Services
{
[Service]
[IntentFilter(new[] { "com.google.android.gms.wearable.BIND_LISTENER" })]
public class WearService : WearableListenerService, ScoreKeeper.Services.IWearService
{
public static string ReceivedData { get; set; }
private GoogleApiClient _client;
const string _syncPath = "/ScoreKeeper/Data";
public override void OnCreate()
{
base.OnCreate();
_client = new GoogleApiClient.Builder(this.ApplicationContext)
.AddApi(WearableClass.API)
.Build();
_client.Connect();
}
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;
}
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);
}
public string GetReceivedData()
{
return ReceivedData == null ? null : ReceivedData;
}
}
}
Android phone (MainActivity)
using System;
using Android.App;
using Android.Content.PM;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using ScoreKeeper.Models;
using ScoreKeeper.Services;
using Xamarin.Forms;
using Android.Support.V4.Content;
using Android;
using Android.Support.V4.App;
using Android.Content;
using ScoreKeeper.Droid.Services;
namespace ScoreKeeper.Droid
{
[Activity(Label = "ScoreKeeper", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
public static string ReceivedData { get; set; }
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
Forms.Init(this, savedInstanceState);
LoadApplication(new App());
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.WriteExternalStorage) != (int)Permission.Granted)
{
ActivityCompat.RequestPermissions(this, new string[] { Manifest.Permission.WriteExternalStorage }, 0);
}
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadExternalStorage) != (int)Permission.Granted)
{
ActivityCompat.RequestPermissions(this, new string[] { Manifest.Permission.ReadExternalStorage }, 0);
}
IntentFilter filter = new IntentFilter(Intent.ActionSend);
MessageReciever receiver = new MessageReciever(this);
LocalBroadcastManager lbm = LocalBroadcastManager.GetInstance(this);
lbm.RegisterReceiver(receiver, filter);
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
public void Process(Intent intent)
{
ReceivedData = intent.GetStringExtra("WearMessage");
WearService.ReceivedData = ReceivedData;
}
internal class MessageReciever : BroadcastReceiver
{
MainActivity _main;
public MessageReciever(MainActivity owner) { this._main = owner; }
public override void OnReceive(Context context, Intent intent)
{
_main.Process(intent);
}
}
}
}