Weird issue with using float variable in settings?

Long story short, I ran into what seems like a bug or I'm missing something obvious (always possible).  I'm not unfamiliar with coding although this is my first ConnectIQ foray.  I'm writing a datafield to help me with my ultras (basically does a configurable long term rolling backwards pace average and projects forward with a finish time and remaining time and warnings on too fast/slow for target finish time) and had some problems.  My datafield ran fine in the simulator but when I took it into the watch, Fenix 6x, it immediatly went ! on me.   I started throwing in "I got to here" print lines and logging them out till I found where it was crashing and it was in the initialize. 

TL/DR; trying to use a variable that pulls its value from a property in the property.xml that was a Float crashed the datafield on the watch but not in the simulator.  And it's only for that one single Float that came in through configuring the datafield using the phone.  I had to cast the value specifically as a float using .toFloat to get rid of the exception. 

Longer story with more details - 

In my config I have the following - 

property.xml:   

    <property id="propTotalDistance" type="float">100.0</property>

settings.xml: 

    <setting propertyKey="@Properties.propTotalDistance" title="@Strings.settingTotalDistance">

    <settingConfig type="Numeric" readonly="false"></settingConfig>

    </setting> 

pre-initialize: 

    hidden var propTotalDistance = App.getApp().getProperty("propTotalDistance"); //get target race distance from user config

    hidden var totalDistance = 0.0; //preset with float

Initialize: 

targetDistance = propTotalDistance * 1609.344; // covert to meters

And that initialize line threw an exception, trying to propTotalDistance in any calculation crashed the datafield on the watch.   Through printing the values I confirmed that targetDistance = 0.00000 before I tried to set it which seems to indicate it's a valid Float.  I was able to print propTotalDistance and get 100.00000 also indicating it seemed to be a float.  This was confirmed in both the simulator through printing to log file on the watch.   So setting a float to the result of multiplying a float with a float doesn't seem like it should cause a problem? 

Yet when I tried to set targetDistance to propTotalDistance in any fashion, the datafield on the watch would crash.  Ran just fine in the simulator. 

The fix/workaround I eventually tried that worked was to change the offending line to this:

targetDistance = propTotalDistance.toFloat() * 1609.344; // covert to meters

And it worked fine.  It's the only float I have in my settings for now so the workaround is okay if a bit goofy to me. 

But is this a bug or am I doing something stupid?  I don't have to do a .toFloat for any other number or equation and I have a lot of them, it's only when trying to use a Float that came in from the settings options is it crashing on the watch. 

  • Long story short:

    1) The Garmin Connect app has a known bug/quirk where float settings are returned as strings. IIRC this happens on Android but not iOS. Who knows what the Connect IQ app is doing.... >_>

    2) It's impossible to tell from just System.println(propTotalDistance) the difference between the number 100.00000 and the string "100.00000", because System.println() would print them both as: 100.000000. You could print the result of propTotalDistance instanceof Toybox.Lang.Float

    3) Basically the workaround is to use toFloat() on all your float settings. In any other environment this wouldn't be a big deal, but it kinda sucks for CIQ, where literally every byte counts, given that data fields have as a little as 16 or 32 KB of available RAM, depending on the watch.

    I think this is covered in the new developer FAQ:

    https://forums.garmin.com/developer/connect-iq/w/wiki/4/new-developer-faq

    Another thing to watch out for is if the user enters an invalid value (e.g. a string where a number was expected, or an out-of-range number), Garmin Connect will send the app null, as opposed to the default value (which is what the user will see if they close the settings and re-open them.) This is why you may see some devs recommend that users send settings a second time if the app crashes or the settings don't seem to "take".

    ^ At least that was the behaviour the last time I checked.

  • Yes.  Based on the version of GCM, this has been around since settings was introduced.  There's a section in the New Developer FAQ about it: https://forums.garmin.com/developer/connect-iq/w/wiki/4/new-developer-faq#settings-crash

    With a simple wrapper function that insures an app gets the correct type for a setting.  It's easier than adding tofloat(), toNumber(), etc within your code.

  • Thanks guys, that explains it and I'm glad it's an actual bug.I was sanity check the user values for everything except if the returned value was actually the types specified because never trust a user to not input weird values.    

    Ill implement a read property and fix broken values set of functions for all the data types Im using.  

     

  • Here's a newer version of readKeyInt that handles both Application.Properties as well as getProperites, and the exception thrown if a key doesn't exist with Application.Properties.  I set hasNewStorage using a "has" in initialize() (the check only needs to be done once, not each time the function is called)

    hasNewStorage=(App has :Storage);

    	function readKeyInt(myApp,key,thisDefault) {	
    	    var value=null;
    	    if(hasNewStorage) {
    	    	try {
    	    		value = App.Properties.getValue(key);
    	    	} catch(e) {
    	    		value=null;
    	    	}
    	    } else {value = myApp.getProperty(key);} 	
    	    if(value==null || !(value instanceof Number)) {
    			if(value!=null) {value=value.toNumber();} 
    			else {value=thisDefault;}
        	}    	
    		return value;    
    	}