I see a few occurrences of a weird error in the ERA related to the onSettingsChanged() method in datafield. All the supported by my app devices with different firmware versions and languages are affected. However, I was never able to reproduce it on my own Edge device. Here is a shortened version of the code where it happens:
import MonkeyMath; import Toybox.Activity; import Toybox.Application; import Toybox.Graphics; import Toybox.Lang; import Toybox.Math; import Toybox.UserProfile; import Toybox.WatchUi; public class Power extends Component { private var _ftp as Number; private var _powerZoneLimits as Array<Number>; private var _powerContainer as RollingAverage?; private var _powerLabelDrawable as Text?; public function initialize() { Component.initialize(); _ftp = getNumberProperty("ftp"); _powerZoneLimits = computePowerZoneLimits(); var powerAvgSeconds = getNumberProperty("powerAvgSeconds"); _powerContainer = powerAvgSeconds > 1 ? new RollingAverage(powerAvgSeconds) : null; } public function compute(info as Info) as Void { ... } public function onLayout(dc as Dc, view as View) as Void { _powerLabelDrawable = view.findDrawableById("powerLabel"); var powerLabelText = Lang.format(Application.loadResource(Rez.Strings.power), [ getNumberProperty("powerAvgSeconds"), ]); _powerLabelDrawable.setText(powerLabelText); } public function onUpdate(dc as Dc, textColor as ColorType) as Void { ... } public function onSettingsChanged() as Void { _ftp = getNumberProperty("ftp"); _powerZoneLimits = computePowerZoneLimits(); var powerAvgSeconds = getNumberProperty("powerAvgSeconds"); _powerContainer = powerAvgSeconds > 1 ? new RollingAverage(powerAvgSeconds) : null; var powerLabelText = Lang.format(Application.loadResource(Rez.Strings.power), [powerAvgSeconds]); _powerLabelDrawable.setText(powerLabelText); // The Unexpected Type Error arises here according to ERA } }
So it points me to the Text.setText() method. But how is it even possible?
Even if we assume that ERA points to the culprit code row not precisely, I don't see the problem in a few previous rows as well.
For the lines 41, 43 the getNumberProperty() method looks like below:
public function getNumberProperty(propertyKey as String) as Number { try { var value = Properties.getValue(propertyKey).toNumber(); return value != null ? value : SETTINGS_DEFAULTS.get(propertyKey); } catch (e) { return SETTINGS_DEFAULTS.get(propertyKey); } } public static const SETTINGS_DEFAULTS = { "ftp" => 200, "powerAvgSeconds" => 3, } as Dictionary<String, Number>;
I think it covers any "unhappy" scenario during the property retrieval:
- If Properties.getValue() fails internally then the catch block will return the default prop value from the constant dictionary;
- If Properties.getValue() returns null then the NPE will be thrown and the catch block will return the default prop value from the constant dictionary;
- If Properties.getValue() returns a String that is not parsable to Number then, according to the documentation, toNumber() will return null and the default prop value will be taken from the constant dictionary;
- If the Properties.getValue() returns a parsable String then it will be successfully cast to Number (the behavior that we usually see when changing settings from Android);
- If Properties.getValue() returns a Number then it will be successfully returned as is after the toNumber() invocation.
Line 42 cannot be a culprit as well, or in another case, the ERA would show a deeper stack trace.
Line 44 looks also fine - all types seem to be correct and, if something were wrong inside the new RollingAverage() constructor, then the stack trace would point inside the constructor.
Line 45 - simple Lang.format() call with 2 non-null arguments.
Line 46 - simple Text.setText() call with String argument since Lang.format() always returns a String.
My simplified properties file looks like this:
<properties>
<property id="ftp" type="number">200</property>
<property id="powerAvgSeconds" type="number">3</property>
</properties>
The simplified settings file:
<settings>
<setting propertyKey="@Properties.ftp" title="@Strings.ftp">
<settingConfig type="numeric" min="50" max="1000"/>
</setting>
<setting propertyKey="@Properties.powerAvgSeconds" title="@Strings.powerAvgSeconds">
<settingConfig type="numeric" min="1"/>
</setting>
</settings>
It defines that the type of settings are numeric and that they may have some min and max values.
The simplified strings file:
<strings>
<!-- settings labels -->
<string id="ftp" scope="settings">Functional threshold power</string>
<string id="powerAvgSeconds" scope="settings">Number of seconds to average current power</string>
<!-- display labels -->
<string id="power">Power $1$s</string>
</strings>
I'm totally lost in assumptions about what may be wrong here and how to reproduce the problem.