Storage querk

I have noticed in the Programmers manual that one can save a dictionary or array in Toybox::Application::Storage by using setValue(key,object), but you cannot get the dictionary or array back using getValue(key) as it throws an exception.

Pardon me if I think this is a little weird.  I do not want to write this dictionary to Storage by descending down and creating keys that contain the hierarchy.

Has anyone any clues as to how to get around this limitation?  Are there any plans to fix this in later releases?

Does the simulator also create the .idx and .dat files used by Storage, and if so, where are they actually written in macOS.

  • I'm testing on a PC currently, but the Storage module is working as expected for me.

    using Toybox.Application;
    using Toybox.Application.Storage;
    using Toybox.Lang;
    using Toybox.System;
    using Toybox.WatchUi;
    
    (:test) module StorageTests
    {
        // deep compare dicts `val1` and `val2`, returns true if equals
        function verifyDictEquals(logger, val1, val2) {
            var val1_sz = val1.size();
            var val2_sz = val2.size();
            if (val1_sz != val2_sz) {
                return false;
            }
            
            var keys = val1.keys();
            for (var i = 0; i < val1_sz; ++i) {
                var key = keys[i];
                
                if (!verifyValueEquals(logger, val1[key], val2[key])) {
                    return false;
                }
            }
            
            return true;
        }
        
        // deep compare arrays `val1` and `val2`, returns true if equals
        function verifyArrayEquals(logger, val1, val2) {
            var val1_sz = val1.size();
            var val2_sz = val2.size();
            if (val1_sz != val2_sz) {
                return false;
            }
             
            for (var i = 0; i < val1_sz; ++i) {
                logger.debug(i.toString());
                if (!verifyValueEquals(logger, val1[i], val2[i])) {
                    return false;
                }
            }
            
            return true;
        }
    
        // deep compare `val1` with `val2`, returns true if all of these hold true
        //
        //   1) value types are known
        //   2) value types are the same
        //   3) values are equal
        //
        function verifyValueEquals(logger, val1, val2) {
        	logger.debug(Lang.format("Expected '$1$', got '$2$'.", [ val1, val2 ]));
    
            if ((val1 instanceof Lang.Dictionary)
             && (val2 instanceof Lang.Dictionary)) {
                return verifyDictEquals(logger, val1, val2);            
            } else if ((val1 instanceof Lang.Array)
                    && (val2 instanceof Lang.Array)) {
                return verifyArrayEquals(logger, val1, val2);            
            } else if ((val1 instanceof Lang.String)
                    && (val2 instanceof Lang.String)) {
                return val1.equals(val2);
            } else if ((val1 instanceof Lang.Double)
                    && (val2 instanceof Lang.Double)) {
                return Math.abs(val2 - val1) < 0.001;
            } else if ((val1 instanceof Lang.Float)
                    && (val2 instanceof Lang.Float)) {
                return Math.abs(val2 - val1) < 0.001;
            } else if ((val1 instanceof Lang.Long)
                    && (val2 instanceof Lang.Long)) {
                return val1 == val2;            
            } else if ((val1 instanceof Lang.Number)
                    && (val2 instanceof Lang.Number)) {
                return val1 == val2;
            } else if ((val1 instanceof Lang.Boolean)
                    && (val2 instanceof Lang.Boolean)) {
                return val1 == val2;
            }
    
            return false;
        }
        
        function testStorage(logger, key, value) {
            Storage.setValue(key, value);
            
            // recursively compare objects by type and value
            var result = verifyValueEquals(logger, value, Storage.getValue(key));
            
            Storage.deleteValue(key);
            
            return result;
        }
        
        (:test) function testDictStorage(logger) {
            return testStorage(logger, "dict", { "key" => "value" });
        }
    
        (:test) function testArrayStorage(logger) {
            return testStorage(logger, "array", [ "element" ]);
        }
    }
    
    class StorageApp extends Application.AppBase {
    
        function initialize() {
            AppBase.initialize();
        }
    
        function getInitialView() {
            return [ new WatchUi.View() ];
        }
    
    }
    

    We have tests similar to this that run on Windows, MacOS and Linux, and they pass in automated testing. I'm not sure why you're seeing a failure that we are not seeing ourselves.

    As for your second question.. yes, the simulator creates the .idx and .dat files on all platforms. The files are stored in the GARMIN subfolder of your temp folder. This should be $TEMP/GARMIN or $TMP/GARMIN on MacOS.

  • Is your key a symbol? Ex setValue(:myKey, value) 

    The documentation states that "Symbols can change from build to build and are not to be used for for Keys or Values". So essentially, if you have a symbol (which is essentially just a number after the the code is compiled), then the key that you are trying to access will not be the same between builds, and you will get null data back, potentially leading to a null pointer exception. Simple fix: change to an enum or string for the key

  • What exception are you getting? You cannot use arrays or dictionaries as keys, but you should definitely be able to save and restore them.

    https://developer.garmin.com/downloads/connect-iq/monkey-c/doc/Toybox/Application/Storage.html#getValue-instance_method

    Keys can be of the following types:

    https://developer.garmin.com/downloads/connect-iq/monkey-c/doc/Toybox/Application/Storage.html#setValue-instance_method

    Values can be of the following types:

    Values can also be of type Array or Dictionary containing the above listed types, excluding BitmapResource. There is a limit on the size of the Object Store of approximately 100KB. If you reach this limit, the value will not be saved and an exception will be thrown

  • HI,

    thanks for all this,

    but in case of an Array, how is the syntax to get/update one particular cell of an Array, since something like

    Storage.getValue("Array[0]')

    try to only get the storage var named "Array[0]" ?

    I cansee in the Simu -> Ctrl+S (edit persistent app Store data) that my array is perfetly created,with the erfect dscription.

    but no chance to get into it.

    Do i have to update the entire key ?

    I made an array of array, i'd like to update / save only specific entry, not the entire key. I just can't do it now without getting the key, updating the particular data, setting the new data in the key. It's time consuming !

    thanks.for your lights !

  • Yes, you save and get by key.  If you saved the array with the key "myArray", that's what you get back when you get that value.  it's the whole array.  After you get it, you can use the value of of a specific index.

    So, when your app starts, you getValue the whole array, and if something changes, you setValue the whole array  

  • but in case of an Array, how is the syntax to get/update one particular cell of an Array, since something like

    Storage.getValue("Array[0]')

    // set
    var array = [ 1, 2, 3 ];
    Storage.setValue("myArray", array);
    
    // get
    array = Storage.getValue("myArray");
    var value = array[0];

  • thanks.

    it confirms what i supposed.