User Seting Data Types

SO frustrating. A user of one of my data fields tells me it crashes every time he changes a User Setting.

Of course sending him a Built for Device version to generate debug line numbers won't work... because User Settings aren't supported in that type of build.

He runs an Edge 1000, which is CIQ 2.4 based. It works perfectly on my CIQ 3.0 devices, and even in the simulator as a 2.4 based 1000.

Here is my theory.... User Settings in a 2.4 device come thru as a STRING, even if they are defined as numbers. So a type mismatch crash happens.

I can easily add toNumber, toFloat, etc in the User Setting retrieval code to force it to the right type, if this is a known variance in 2.4 devices, and a known bug in the simulator.

UPDATE: adding toNumber apparently ISNT that easy. Ugh. I'm clearly doing something strange here but don't get it.

In a function - to show that toNumber() is a known symbol:
==================
var x = 5.3;
var y = x.toNumber();
System.println("X: " + x + " Y: " + y);
User[:lapFlag] = getP.getProperty("lapFlag_prop").toNumber();

Output:
==================
X: 5.300000 Y: 5
Could not find symbol 'toNumber'
Symbol Not Found Error
  • This sure sounds like what's in the New Developer FAQ, point 10.
  • After a quick check in the sim, I believe the precise error message (Symbol Not Found) you are getting is because lapFlag_prop is defined as a bool. toNumber() does not exist for bool types.

    BTW, the type of the lapFlag_prop property is the kind of information that would be helpful when posting code. I sort of made an educated guess (which could still be wrong.)

    So, yes, you do need to check for null and perform the redundant conversion as outlined in the FAQ, point 10, but you also need to make sure that your property is a non-null string, float or integer if you want to call toNumber(). toNumber() will also fail on null (with an "Unexpected Type" message.)

    I will also add that the null check, as outlined in the FAQ, is absolutely necessary (even on iOS), because of a bug/design issue that has been reported. The issue is that if a non-number (such as a blank string, “abcd”, or “1a”) is accidentally entered in a numeric field, GC iOS will apparently send back a null. As users have no way of knowing for sure (in general) which fields are numeric and which are not, and as GC iOS sends back null instead of just validating/restricting the input, this is a real case you have to handle. There’s a whole bunch of issues with this IMO, but it is what is for now.
  • Wait there is an FAQ! Thanks all. And now it makes sense about the boolean, but I guess I expected a type mismatch error. toNumber symbol not found seemed to suggest to me that the method wasn't found. Anyway, I'll go read the FAQ now and I'm sure I won't need to post as often :-)
  • Yes, point 10 was a very frequent question when app settings were introduced back in CIQ 1.2.0. I can't even guess how many times Travis and I posted similar code for it.

    readKeyInt() is in every app I have that uses app settings, and I have a similar readKeyBoolen() for those. With Application.Properties, there's a bit of a difference, as if a property doesn't exists, you don't get a null as you do with getProperties(), but it throws an exception, so I use try/catch for that.

    update: This could also be connected to why in some apps, you see that only Garmin Express should be used to change settings. I've only ever seen the problem reported with GCM, and GE has just always worked correctly.
  • toNumber symbol not found seemed to suggest to me that the method wasn't found.


    Well, in a way it wasn't, but I agree that the error message may have been misleading.

    You may wish to file a bug report / enhancement request in the bug reports forum, asking for a more descriptive error message, such as "symbol not found for class X".
  • DaveBrillhart a slightly more efficient version (memory/code-space-wise) of that code. (In Monkey C, the ternary operator is more efficient than the corresponding if statement.)

    // app is passed in for efficiency's sake
    function getBooleanProperty(app, boolProp) {
    return app.getProperty(boolProp) ? true : false;
    }
  • One liners...

    var User = { :lapFlag => true, :minRPM => 65, etc }

    module UserSettings {
    function getSettings() {
    var uv, getP = Application.getApp();


    uv = getP.getProperty("advPwrFlag_prop"); User[:advPwrFlag] = (uv != null) ? uv : User[:advPwrFlag];

    uv = getP.getProperty("minRPM_prop"); User[:minRPM] = (uv != null) ? uv.toNumber() : User[:minRPM];

  • DaveBrillhart see my response above for code that is more efficient (memory/code-space-wise) for the boolean case.

    The code you posted for the Number case really has no advantage over the code in the FAQ. Your code also misses the case when your property actually is a number in the first place. In that case, you're just ignoring the property, which is probably not what you wanted to do.

    This might work better.
    uv = getP.getProperty("numerical_prop");
    User[:numerical_prop] = (uv != null) ? uv.toNumber() : null; // this assumes that uv is a string, number or float


    I would argue that those are not one-liners. You can put any amount of code on a single line (including your entire program), but traditionally:
    - Each statement (ending in a semicolon) goes on its own line
    - The if condition goes on its own line

    Those are really 3 liners. By cramming 3 lines of code onto one line, you are only making it harder to read and understand.
  • Thx! That is better. Except I'd resort to the default rather than null, and the toNumber() doesn't work for booleans. But I get the point. #alwaysLearning


    uv = getP.getProperty("Value_prop"); User[:Value] = (uv != null) ? uv.toNumber() : User[:Value];
  • Thx! That is better. Except I'd resort to the default rather than null, and the toNumber() doesn't work for booleans. But I get the point. #alwaysLearning


    uv = getP.getProperty("Value_prop"); User[:Value] = (uv != null) ? uv.toNumber() : User[:Value];


    1) Yeah, the default value over null decision is def valid, especially since null may have no meaning for your app. And also because the only way you can receive null for a numerical property is when the user accidentally sets a non-numerical value in the GC app settings. (This is an issue which I have reported.)

    2) If you're dealing with lots of properties, it might be better to use helper functions, especially if you've got all the extra error-checking and default value handling. It also makes your code more generic for anyone who is reading (and for your own reuse), rather than being specific for your app.

    3) Yeah toNumber() doesn't work for bools, but the idea here is that you know how you defined your properties. If you really want to write very generic code (which takes up more memory), here's an alternative.


    function getNumericalProperty(app, propName, defaultValue) {
    var propVal = app.getProperty(propName);
    return (propVal != null && !(propVal instanceof Lang.Boolean)) ? propVal.toNumber() : defaultValue;
    }


    Hopefully I haven't missed any other kinds of non-null properties (besides bool) which lack the toNumber() method. If I did, you would need to modify the function appropriately, if you are concerned about bullet-proofing your code. A different way to do it would be to explicitly check for Number, Long, Float, Double and String (and convert where necessary/desired) which would of course make the code even more inefficient. But for most apps it wouldn't matter.