Unexpected feature of getProperty - passes value by reference not value

AppBase:getProperty() passes object values by reference not by value, so the property is modified when the returned value is modified.

myApp.setProperty("test", [1,2,3]);
var testValue = myApp.getProperty("test");
System.println("test"+ myApp.getProperty("test"));
testValue.add(4);
System.println("test"+ myApp.getProperty("test"));

returns:
test[1, 2, 3]
test[1, 2, 3, 4]


I wonder if this is a bug or an undocumented feature?
  • Is it saved? What is "test" the next time you run (before you do the setProperty). If it's changed, I'd call it a bug for sure. If not, I'd just call it sloppy code that might not work the same in the future. :)
  • Hmmm, interesting.

    First "next time" it gets updated.
    The following "next times" it doesn't.
    Looks like sloppy code!
  • Given how the language works, this does not surprise me.

    MonkeyC objects are reference counted. If you have a Lang.Array associated with two variables, modifications to one variable are reflected in the other...

    var a = [ 1, 2, 3 ];
    var b = a;

    Sys.println<{"b=" + b}>;
    a.add(0);
    Sys.println<{"b=" + b}>;


    In the case shown in the original post, one reference is the local testValue variable and the other is the reference stored inside the Lang.Dictionary that stores the app properties.

    Is it saved?

    The documentation doesn't say when the values will be saved, but it makes sense that the value that is in effect when the properties are saved would be the value you'd get when starting the application the next time. Based on my understanding of the implementation, the app properties are only saved when the app exits. So the last value of the variable should be getting stored to flash.

    Hmmm, interesting.

    First "next time" it gets updated.
    The following "next times" it doesn't.
    Looks like sloppy code!


    The code you posted above always sets the property to [1, 2, 3], so I'd expect that you'd always see [1, 2, 3] as the first value displayed. It sounds like this is what you are seeing.

    With a few small changes you can see that the last displayed value is the one that is stored. Have a look at this test program. Every time you tap the screen or press the enter key the stored value will be incremented, but no call to setProperty("test", ...) is being made after the first execution...

    using Toybox.Application as App;
    using Toybox.System as Sys;
    using Toybox.WatchUi as Ui;
    using Toybox.Graphics as Gfx;

    class Delegate extends Ui.BehaviorDelegate
    {
    function initialize<{}> {
    BehaviorDelegate.initialize<{}>;
    }

    function onSelect<{}> {
    Ui.requestUpdate<{}>;
    return true;
    }
    }

    class View extends Ui.View
    {
    function initialize<{}> {
    View.initialize<{}>;
    }

    function onUpdate<{dc}> {
    dc.setColor<{Gfx.COLOR_WHITE, Gfx.COLOR_BLACK}>;
    dc.clear<{}>;

    var cx = dc.getWidth<{}> / 2;
    var cy = dc.getHeight<{}> / 2;

    var testValue = App.getApp<{}>.getProperty<{"test"}>;

    Sys.println<{"testValue=" + testValue}>;
    testValue[0] += 1;
    Sys.println<{"testValue=" + testValue}>;

    dc.drawText<{cx, cy, Gfx.FONT_LARGE, testValue.toString<{}>, Gfx.TEXT_JUSTIFY_CENTER}>;
    }
    }

    class App extends App.AppBase
    {
    function initialize<{}> {
    AppBase.initialize<{}>;
    }

    function onStart<{state}> {
    var testValue = getProperty<{"test"}>;
    if <{testValue == null}> {
    testValue = [1];
    setProperty<{"test", testValue}>;
    }

    // you must set a property so that the properties are saved on exit
    setProperty<{"unused", 1}>;
    }

    function getInitialView<{}> {
    return [ new View<{}>, new Delegate<{}> ];
    }
    }


    It should be noted that you must make a change to the object store (via setProperty() or deleteProperty()) at some point in the program execution for the property value to be saved. It doesn't matter what property you change/delete, you just have to make a change to the underlying dictionary. This might seem weird, but it just means that the implementation is trying to minimize writes to the flash.

    I agree that this is very interesting behavior. It all makes sense given my understanding of how things work, and there are good reasons that things work the way that they do. It seems that any change that would fix this problem would result in an additional cost. I'm curious to see what the Garmin guys have to say about this issue.
  • Former Member
    Former Member over 7 years ago
    My gut says that we shouldn't let this happen, but Travis is 100% correct about why it does.

    To prevent this, we would need to make a deep copy of any non-basic types that are retrieved from the object store, which would cost the application more memory. We will have to discuss the best way to handle this.
  • Hey Travis, (dumb question?) could you explain your use of <{}> as in
    function initialize<{}> please?
  • Simple.. It's so he can post using a code block with the new forum SW. Think of them as parens...... Using parens in code blocks isn't handled correctly with the new forum SW. yes it's a pain!
  • That's odd, it works for me...see my initial post on this thread and below!
    function testParens(){
    //demo code
    tryThis(param1);
    return;
    }

  • Yes, it works in some cases and breaks in others. Try it yourself; fix the below snippet and try to post it...

    if <{x != null}> {}

  • You should get an Error while saving content: SyntaxError: Unexpected token A in JSON at position 1 error when you try to post the above code. If you simply preview it, you'll get an Error Loading Preview error.

    The problem isn't isolated to if statements either. Sometimes other bits of code trigger the problem. For example, if I use the testParens function you posted above, but I put a space between the closing parenthesis of the argument list and the opening curly brace of the function body, I'll get the same error.

    At some point it becomes easier to just replace parens with something else (at least until the forums software handles code snippets properly).
  • What a shame that we have to spend time fighting and explaining the forum upgrade failure where we could have been discussing what really matters.