Status Update
Comments
od...@google.com <od...@google.com> #2
setDynamicValueInvalidationFallback
does not prevent the data source from being destroyed, the fact that onDestroy
is called is unrelated to this usage. The app in fact should be destroyed right after providing the complication data, because the app is no longer needed (that's also the point of using platform bindings such as heartRateBpm
instead of providing the value itself - the platform can update the value while the app is not running).
setDynamicValueInvalidationFallback
is used when the platform can't evaluate the data, which is normally the case for heart rate binding at the moment before the first data arrives from Wear Health Services (WHS), and depending on the manufacturer that can be the case until the watch is worn (if the watch was off-wrist when the data source provided the data).
In the case where the fallback data is used (the case described in the previous paragraph), it is sent as a NoDataComplicationData
where the fallback is put inside the placeholder
field. Some watch faces wrongly don't render this placeholder, in which case you will see an empty complication instead of the fallback, which is the bad user experience you observe.
Thanks for reporting and sorry about the bad experience, this is most likely working as intended. Please reopen if I misunderstood the issue or follow up with any more questions!
ma...@gmail.com <ma...@gmail.com> #3
Oh, that makes sense. I've tested it and it works like you wrote but I've noticed one thing.
Used
.setDynamicValueInvalidationFallback(
ShortTextComplicationData.Builder(
text = PlainComplicationText.Builder("--").build(),
contentDescription = PlainComplicationText.Builder(text = getString(R.string.complication_name)).build())
.setMonochromaticImage(MonochromaticImage.Builder(image = Icon.createWithResource(this, icon)).build())
.setTapAction(openScreen())
.build()
)
AND Watch Face Format watch face with this NO_DATA reference. = <Complication type="NO_DATA">
Inside PartImage
with [COMPLICATION.MONOCHROMATIC_IMAGE] and PartText
with [COMPLICATION.TEXT]
. Strangely, only Icon (Monochromatic Image) is being rendered. Text not. Looks like text is not being forwarded as a fallback to the placeholder. Can you check this, or am I doing it wrong?
Thanks for your explanation!
od...@google.com <od...@google.com>
od...@google.com <od...@google.com> #4
Can you try to provide that ShortTextComplicationData
you are building in the fallback but directly (not as a fallback), and see if the result is the same? It's possible that the watch face is not rendering the complication the way you expect.
ma...@gmail.com <ma...@gmail.com> #5
I'm doing it and when ShortTextComplicationData
is used directly, everything works. Problem is only when it is passed to the setDynamicValueInvalidationFallback.
This is code for data:
ShortTextComplicationData.Builder(
text = DynamicComplicationText(dynamicString,"--"),
contentDescription = PlainComplicationText.Builder(text = getString(R.string.complication_name)).build())
.setMonochromaticImage(MonochromaticImage.Builder(image = Icon.createWithResource(this, icon)).build())
.setTapAction(openScreen())
.setDynamicValueInvalidationFallback(
ShortTextComplicationData.Builder(
text = PlainComplicationText.Builder("--").build(),
contentDescription = PlainComplicationText.Builder(text = getString(R.string.complication_name)).build())
.setMonochromaticImage(MonochromaticImage.Builder(image = Icon.createWithResource(this, icon)).build())
.setTapAction(openScreen())
.build()
)
.build()
This is dynamicString: `PlatformHealthSources.heartRateBpm().format()
How it works, I'm wearing watch on wrist, icon + heart rate is showing (heart rate updates correctly).. When I take watch off wrist - zero is showed.. so far, still ok. Now I manually refresh complication complication from the app UI, or activate it in another watch face or activate and deactivate it in the same watch face. (just needs onComplicationRefresh) Result = only Icon is showing, Text should be reporting "--" but instead is null or ""
This was tested with Watch Face Format and ComplicationSlot with [COMPLICATION.NO_DATA], but, recently I got an information that NO_DATA is not supported by watch face Validator so hard to say if this may affect this.
od...@google.com <od...@google.com> #6
Can you run adb shell setprop log.tag.ComplicationData DEBUG && adb shell setprop log.tag.WearServices DEBUG
, reproduce it, then upload a bug report?
Also try changing --
to something else - it's slightly possible the watch face has special handling for that value. Also, try sending NoDataComplicationData(placeholder = ...)
directly and see if it behaves the same, maybe the watch face doesn't handle the placeholder correctly.
Note that the 0
you see when the watch is off-wrist is not the fallback - the expression is not invalidated, instead there's a separate binding you should use to check the accuracy of the sensor (which I'm guessing reports 0 when it's off-wrist). See PlatformHealthSources.heartRateAccuracy()
, and consider conditioning on that value and only show the bpm when it's medium or high.
ma...@gmail.com <ma...@gmail.com> #7
Actually, I'm using this for dynamicString
private val dynamicString: DynamicString =
DynamicString.onCondition(
PlatformHealthSources.heartRateBpm().eq(0F)
.or(heartRateAccuracy().lt(PlatformHealthSources.HEART_RATE_ACCURACY_MEDIUM)))
.use(DynamicString.constant("--"))
.elseUse(PlatformHealthSources.heartRateBpm().format())
so when there is no HR, "--" is showing and it is really showing. I'm also creating this watch face so I know when it shows correct data and it is handling "--" just fine. Problem is only when complicationRefresh happens and watch is off wrist.
I'll try to provide bug reports asap
ma...@gmail.com <ma...@gmail.com> #8
Quick update, tried returning NoComplicationData directly
return NoDataComplicationData(placeholder = ShortTextComplicationData.Builder(
text = PlainComplicationText.Builder("--").build(),
contentDescription = PlainComplicationText.Builder(text = getString(R.string.complication_name)).build())
.setMonochromaticImage(MonochromaticImage.Builder(image = Icon.createWithResource(this, icon)).build())
.setTapAction(openScreen())
.build()
)
This is what logcat is showing for that complication slot
.DeclarativeWatchFaceRuntime0@81d8e54
2023-11-10 16:58:00.031 17287-17287 DWF:WearCo...onProvider com.google.wear.watchface.runtime I [12:NO_DATA] NoDataSource
2023-11-10 16:58:00.031 17287-17287 DWF:WearCo...onProvider com.google.wear.watchface.runtime I [12:TEXT] "/EMPTY/"
2023-11-10 16:58:00.031 17287-17287 DWF:WearCo...onProvider com.google.wear.watchface.runtime I [12:TITLE] "/EMPTY/"
Hmm..
od...@google.com <od...@google.com> #9
In the test in
ma...@gmail.com <ma...@gmail.com> #10
Icon stayed, text disappeared. Tap Action still works.
I was told that NO_DATA is actually not accepted by WFF Validator so that's maybe why?
Attached is bugreport. See that watch reports Text like 81...82...83.. (SLOT 12).. then watch is off wrist, (DynamicString.constant("--")) is used, so far ok, then I'll force complication Update, NoDataComplicationData is passed, text should be still "--" but instead is "/EMPTY/" and it simply disappears
Maybe this will help too, complication slot code:
<ComplicationSlot name="ROW_2_1"
alpha="255"
displayName="ID_COMPLICATION_2"
height="50"
isCustomizable="TRUE"
slotId="1"
supportedTypes="SHORT_TEXT RANGED_VALUE EMPTY"
width="180"
x="225"
y="92">
<DefaultProviderPolicy defaultSystemProvider="SUNRISE_SUNSET"
defaultSystemProviderType="SHORT_TEXT" />
<BoundingRoundBox cornerRadius="10" height="50" width="180" x="0" y="0" />
<Complication type="SHORT_TEXT">
<Group name="Icon_ea05"
alpha="255"
height="34"
pivotX="0.5"
pivotY="0.5"
tintColor="[CONFIGURATION.themeColor.0]"
width="34"
x="12"
y="8">
<PartImage name="Icon_ea052"
height="34"
pivotX="0.5"
pivotY="0.5"
width="34"
x="0"
y="0">
<ImageFilters>
<HsbFilter brightness="1" hueRotate="0" saturate="0" />
</ImageFilters>
<Image resource="[COMPLICATION.MONOCHROMATIC_IMAGE]" />
</PartImage>
</Group>
<PartText name="Text_d7ae"
alpha="255"
height="38"
pivotX="0.5"
pivotY="0.5"
tintColor="[CONFIGURATION.themeColor.1]"
width="136"
x="44"
y="6">
<Text align="CENTER" ellipsis="TRUE">
<Font color="#ffffffff"
family="roboto_medium"
size="34"
slant="NORMAL"
weight="NORMAL">
<Template>%s
<Parameter expression="[COMPLICATION.TEXT]" />
</Template>
</Font>
</Text>
</PartText>
</Complication>
<Complication type="NO_DATA">
<Group name="Icon_ea05_2"
alpha="255"
height="34"
pivotX="0.5"
pivotY="0.5"
tintColor="[CONFIGURATION.themeColor.0]"
width="34"
x="12"
y="8">
<PartImage name="Icon_ea052"
height="34"
pivotX="0.5"
pivotY="0.5"
width="34"
x="0"
y="0">
<ImageFilters>
<HsbFilter brightness="1" hueRotate="0" saturate="0" />
</ImageFilters>
<Image resource="[COMPLICATION.MONOCHROMATIC_IMAGE]" />
</PartImage>
</Group>
<PartText name="Text_d7ae_2"
alpha="255"
height="38"
pivotX="0.5"
pivotY="0.5"
tintColor="[CONFIGURATION.themeColor.1]"
width="136"
x="44"
y="6">
<Text align="CENTER" ellipsis="TRUE">
<Font color="#ffffffff"
family="roboto_medium"
size="34"
slant="NORMAL"
weight="NORMAL">
<Template>%s
<Parameter expression="[COMPLICATION.TEXT]" />
</Template>
</Font>
</Text>
</PartText>
</Complication>
<Complication type="EMPTY" />
</ComplicationSlot>
ma...@gmail.com <ma...@gmail.com> #11
Actually, this whole thing around "NO_DATA" supported type is unfortunate, almost no watch face in the play store is using this type. Best would be if that no data can be evaluated as one of the other types supported and developer should decide what is passed to the builder similar to what I'm doing with DynamicString.onCondition but I understand what the intention was.
Only solution for now would be to add some pre-defined NoData Complication.TEXT directly in WFF code, I've tried it, it worked but as I noted before, Validator currently does not accept NO_DATA so watch face could not pass play store review.
od...@google.com <od...@google.com>
ga...@google.com <ga...@google.com> #12
We're currently investigating how this should be handled in WFF. As you've noted, there are some limitations are the moment.
Description
Component used: androidx.wear.watchface:watchface-*
Version used: 1.2.0-rc01
Devices/Android versions reproduced on: Pixel Watch 2 (Wear OS 4)
Description: setDynamicValueInvalidationFallback used in ShortTextComplicationData.Builder and any other complication type with DynamicComplicationText and PlatformHealthSources.heartRateBpm() is not working and instead, onDestroy is firing, making complication disappear until PlatformHealthSources.heartRateBpm() is available again. This happens in scenario when complication update is requested (onComplicationRequest, onComplicationActivated)
Issue happens also with PlatformHealthSources.heartRateAccuracy()
BODY_SENSORS permission given.
Sample code: ComplicationType.SHORT_TEXT
So, instead of using DynamicValueInvalidationFallback, onDestroy fires and complication is not rendered anymore = empty slot.
Based on Documentation:
Sets the complication's fallback, used in case any dynamic value cannot be evaluated, e.g. when a data source is not available.
I've also noticed the same behavior with Fitbit Heart Rate Complication and that it disappears when you set complication and you're actually not wearing watch. Complication slot is simply empty because onDestroy is fired immediately after onCreate.
After first HR measurement available, complication slot is rendered again. It's just bad user experience if complication slot is empty. It should definitely show some fallback ComplicationData.