.NET MAUI - Android Watch Application Showcase (Part 2)

.NET MAUI - Android Watch Application Showcase (Part 2)
January 5, 2024
In part one, we looked at setting up the development environment for writing .NET MAUI applications for the Wear OS platform. After the introduction to the platform and running our first emulation, we can now look at brief tips regarding cross-device communication, event listening, as well as resolving some distribution issues we encountered.

Step 3 – Tips on how to write an Android Wear OS application

The Wear OS application itself is not a .NET MAUI application, but a common .NET Android application. It used to be native Xamarin.Android with some .NET packages. Here you can read more on the differences between Xamarin and MAUI: https://grialkit.com/blog/learn-the-key-differences-between-net-maui-vs-xamarin-forms-for-cross-platform-mobile-and-desktop-development

Thankfully, the project structure is the same – there are Activities in which some UI logic is written, while the XML layout files under Resources/Layout folder define the UI.

Don’t forget to develop a simplistic UI and keep in mind that your users will literally be wearing your application on their wrists. While writing watch apps, maintaining a friendly UX is even more important than for mobile applications. A comprehensive guide throughout Wear OS UI development can be found here: https://developer.android.com/design/ui/wear

How to connect to the Internet on watches

When communicating with online resources, developers don’t need to write their own gateway via a mobile application. The Wear OS itself takes care of it. It offers network connection via direct WiFi or a Bluetooth connection via the connected smartphone. But since managing that is hidden from the Wear OS app developer, what we only see is whether the network connection is available or not.

How to communicate between the .NET MAUI and Wear OS applications

The most important feature for non-standalone Wear OS apps is communication with mobile applications over Bluetooth. As mentioned above, the ongoing watch-smartphone communication is hidden from the developer behind the WearableClass static class. Instead, this API offers the DataClient (among others), which gives us a tool to Publish messages and attach Listeners to listen to messages published by other paired devices.

Publishing data from the watch to the smartphones (or vice versa) can be achieved like so:

var dataMap = PutDataMapRequest.Create(“/specific_path”);
dataMap.DataMap.PutString(“MessageKey”, “Message Value”);
var request = dataMap.AsPutDataRequest();
WearableClass.GetDataClient(Application.Context).PutDataItem(request);

On the other side, we attach a listener onto the same path and read a string message once it arrives:

public override void OnDataChanged(DataEventBuffer dataEvents) {
    foreach (var data in dataEvents) {
if (data.DataItem.Uri.Path != “/specific_path”) continue;
var item = DataMapItem.FromDataItem(data.DataItem);
var message = data.DataMap.GetString(“MessageKey”);
...
break;
    }
}

There are other clients offered by the WearableClass – CapabilityClient, MessageClient, NodeClient, etc. This page on Android Developer Training provides much more information about all of them: https://developer.android.com/training/wearables/data/data-layer

How to read data from the mobile application context

The communication over Bluetooth between mobile and watches is based on messages. You cannot simply “read” data from mobile on watches. You must ask for them and listen for response.

In real scenarios, when Wear OS app starts and needs to synchronize itself with the smartphone:

  1. We must publish a request using PutDataItem (see above) on specific Path in Wear OS app.
  1. On the other side, there must be a listener registered for a specific Path
  1. When the request arrives, it is processed, and the smartphone app must then publish its response on another Path.
  1. On the watch side, we must listen for data on another Path and catch the “response” published by the mobile application.

How to ensure that published messages are delivered to the other device

There is a hidden feature/bug in WearableClass when publishing data. It publishes only data, which has changed since the previous publication. If the data is the same, the WearableClass layer skips the data publication without any warning or response.

So, the best practice to ensure our logic in mobile – watch communication forks fine is to include a timestamp in each data when publishing. In previous example it should looks like:

dataMap.DataMap.PutString(“TimeStamp”, DateTime.UtcNow.Ticks.ToString());
dataMap.DataMap.PutString(“MessageKey”, “Message Value”);

Step 4 - How to start the Watch app when the Android app starts

A common task which improves the UX of our Wear OS app significantly is starting the watch app when the user opens the mobile app. This is not automatically done by WearableClass API. We must take care of it ourselves.

On the main activity of our Android project of MAUI application, we must override the OnResume method and publish a message to all connected nodes (= devices):

protected override void OnResume() {
base.OnResume();

var nodes = await WearableClass.GetNodeClient(this).GetConnectedNodesAsync();

foreach (var node in nodes)
await WearableClass.GetMessageClient(this).SendMessage(node.Id, “/app_starts, null);
}

And in our Wear OS application, we create a StartupListenerService:

[Service(Exported = true, Name= "bundleid.StartupListenerService")]
[IntentFilter(new[] { "com.google.android.gms.wearable.BIND_LISTENER" })]
public class StartupListenerService : WearableListenerService {
 public override void OnMessageReceived(IMessageEvent p0) {
   if (p0.Path.Equals(“/app_starts”)) {
     var intent = new Intent(Application.Context, typeof(MainActivity));
     intent.AddFlags(ActivityFlags.NewTask);
     StartActivity(intent);
   } else {
     base.OnMessageReceived(p0);
   }
 }
}

And register it in the AndroidManifest.xml file:

<application … >
<uses-library android:name="com.google.android.wearable" android:required="true" />
<meta-data android:name="com.google.android.wearable.standalone" android:value="false" />
<service android:name="bundleid.StartupListenerService" android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
</intent-filter>
</service>
</application>

Please note that in the service, we must specify explicitly the Service name (bundleid.StartupListenerService) in ServiceAttribute above the class definition. And the same name must be specified in AndroidManifest.

Step 5 – Integration in .NET MAUI mobile application

We’re building an Android Wear OS supplement for our .NET MAUI application, which is multiplatform and must be working on both platforms – Android and iOS. So, we’ve created platform specific implementations of watch related components defined by its interface.

  • For iOS, we’re ignoring all calls to watches for now – it will be implemented later for Apple Watch OS version.
  • For Android, the implementation of all Listeners and Publishers is done using WearableClass API.

All watch related components were registered in an IoC container and are injected to the rest of mobile app code by their interfaces.

Step 6 – Distribution related issues

After implementing the functionalities and alpha testing on our development environment looked fine, we released the app for further testing into production. In that very moment, a very unfortunate issue was raised in our Wear OS application. The communication over WearableClass stopped working and the watch application stopped responding to messages / data distributed over DataClient.

After a careful investigation of the issue, we found the problem in the signing certificate process.

To enable Wearable Data-layer between the smartphone and watch apps, two conditions must be met:

  1. Both apps must have the same application id (e.g. com.sabo.ias.sabot.mobile)
  1. Both .apk files must be signed with the same keystore certificate.

If we don’t sign our app for beta testing distribution with the same certificate as the watch app, the Data-layer API requests are ignored.

The best solution is to enable “.APK Signature” in Android properties of .NET MAUI application for mobile and .NET Android application for watches and fill Keystore certificate details. For development and QA purposes, untrusted certificates created by the developer may be used (for example during MAUI app distribution over Archiving option in Visual Studio). For production, a publicly trusted developer certificate must be used.

In conclusion, this showcase of our .NET MAUI Android Watch Application highlights the versatile and powerful features of the framework. We hope you enjoyed following through, as well as that you learned something useful for your future endeavours.

Share:
Luboš is an experienced .NET developer (web and mobile apps) very familiar with SQL and DevOps. He developed a lot of web applications used worldwide by industry and the academical world. He holds a bachelor’s degree in applied physics and astrophysics. Likes bowling, rides e-mountainbike, skis and plays the violin.

Article collaborators

SABO Newsletter icon

SABO NEWSLETTER

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

About SABO Mobile IT

We focus on developing specialized software for our customers in the automotive, supplier, medical and high-tech industries in Germany and other European countries. We connect systems, data and users and generate added value for our customers with products that are intuitive to use.
Learn more about sabo