Android Wear 2.0 Architecture issues for realtime complications

653 views Asked by At

I'm developing a set of complications that I would like have regardless of the other installed apps and watch faces. Yes, at some point I am reinventing the wheel, but at the same time I am using this as a learning project. This will also ensure that I always have all the complications I use, available and that they all have the same format and style, instead of relying on 3rd party apps to provide them separately.

The set will have complications for Heart rate, gps coordinates, hours, minutes, seconds, dd/MM date, dd/MM/yy date, battery, etc.

When I started programing all this I found several problematic pieces (most likely because this is the first time I develop complications, or an app for android wear for that matter) and hence this question.

Note that some of this behavior might be specific to the Huawei Watch 2 LTE.

1) Upgrade interval push / pull.

I understand complications as data providers, whose only responsibility is to provide the data to whatever watch face is calling them. This means that we are not certain (and we rely on the watch face developer) to know about the complication and request updates accordingly. This turns some complications completely useless if not updated in time (for example display the Seconds). Could also leave to complications displaying old data (for example old GPS coordinates, old heart rate bpm). So ok, I decided to implement ProviderUpdateRequester with the AlarmManager to push data to the watch face. The problem again, is with complications that should happen faster, like seconds, as Android will block pending intents if they are schedule too often. So in order to get around that, I decided to use Android handlers within the same service instance, which turn out to be not a good idea because of the next topic.

2) Complication lifecycle

By debugging, I found out that the instance of the ComplicationProviderService object that is executing onComplicationActivated, onComplicationUpdate, onComplicationDeactivated can be different. This means that this is not a sticky service (single instance) that will be always running, but every update creates a new instance of the service. This is problematic because of heavy initialization complications: for example GPS, or Heart Rate monitor that need to listen for new values and it might take a while to retrieve the first value. And also, for those complications that can't rely on AlarmManager, and/or need to keep some sort of state between updates executions.

3) Display aware service

To get around the previous point , let's say you have static variables on your complication service , which are initialized onComplicationActivated and disabled at onComplicationDeactivated. For example, this could be getting a reference for the LocationProvider and starting listening for location updates. This will ensure that each invocation to onComplicationUpdate will not have to perform the heavy/cold initialization and will have access to the most up-to-date data. However, this also means that your logic will executed regardless if onComplicationUpdate is called or not. When in ambient mode (or screen off) the watch face can decide not to update the complication by not calling onComplicationUpdate, but it's not aware of our static logic, nor the ComplicationProviderService has a callback invocation for when the screen goes into ambient mode or turns on/off. This is a problem, because in our example, if the screen is off, we are still going to be listening for GPS coordinates, and most likely draining the battery. Sure, we can deal with this by using a combination of BroadcastReceiver (Intent.ACTION_SCREEN_ON/OFF) and DisplayManager.DisplayListener, but then again, not sure if i'm taking the correct path here, because this will mean that we are now creating services that need to be statically aware of the state of the display.

4) Detect screen on/off

The BroadcastReceiver for Intent.ACTION_SCREEN_ON/OFF works as expected when ambient mode is disabled, but it doesn't it's enabled. When ambient mode is enabled, Intent.ACTION_SCREEN_OFF is dispatched when going into ambient mode, but Intent.ACTION_SCREEN_ON is not dispatched when coming out of ambient mode. While a bit more complex, this can be accomplished by using DisplayManager.DisplayListener to get updates on the onDisplayChanged callback.

TL;RD

1) How do you ensure watch faces display your complications in a timely manner to always have correct and most up-to-date information?

2) How do you deal heavy/cold initialization of a ComplicationProviderService if everytime onComplicationUpdate is called the service instance is different?

3) Is making a long running service display-aware something crazy to do?

4) Technically the screen is still on when in ambient mode, so why is Intent.ACTION_SCREEN_OFF being broadcasted? Why isn't Intent.ACTION_SCREEN_ON/OFF symetrical when ambient mode is enabled?

5) Maybe complications shouldn't be use for exposing realtime information?

Thanks a lot

1

There are 1 answers

1
Hoi On BEST ANSWER

A couple of things to unpack:

  • Complications are not meant to be updated frequently (think minutes, not seconds) - this is to preserve battery.
  • ProviderUpdateRequester is designed more for (on average infrequent) irregular updates like messages coming through a chat app.
  • Time dependent complications - there are not an "update" as such but Wear provide ways for developers to count up / down from certain time and for displaying date related field (world clock, day of the month) without the provider sending the system updates all the time. For this last one, please refer to docs for ComplicationText.TimeDifferenceBuilder and ComplicationText.TimeFormatBuilder.

For your use case, a more appropriate thing maybe to consider an always-on app. User uses it for a certain time period for a specific purpose so they explicitly agree to use to use more battery to track things like GPS or heart rate. For example, a lot of running apps on Wear do this.

I hope this helps.