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.