Is there a better way to select resources?

Hi,

I am using a lot of json resources in my fields so as to allow me to rapidly configure them for a range of different situations. The trouble is, I haven't been able to find a way to evaluate a key for the resources...

EG: In other languages, I might have done:

function getResource(key) {

return WatchUi.loadResource(Rez.JsonData[ key ]);

}

But that syntax won't work, so to avoid a big-arsed and ugly if / switch statement running for several pages, I have resorted to...

function getResource(key) {

return WatchUi.loadResource([Rez.JsonData.K0, Rez.JsonData.K1, Rez.JsonData.K2, ... ][key]);

}

I've had to try similar approaches with programmatically selecting symbols.

But...

THAT SEEMS LUDICROUS!

Is there really no way to evaluate a string to return a symbol or a resource?

G

  • You can use resource overrides and/or jungles, so that you build with resources specific to a language or data based on a specfic device for example.  Look in the Programmer's guide.  Her's how to handle french from the PG.  Also, take a look at the "Strings" sample from the SDK.

    Localization Qualifiers

    Localization qualifiers are a way to specify language-specific string resources, and are specified as an ISO 639–2 language code. These qualifiers may be combined with either device or family qualifiers, and are always specified last in the qualifier naming scheme. For example:

    • resources-fre: Provides French language-specific string resources for all devices
    • resources-round-fre: Provides French language-specific string resources for round devices only
    • resources-fenix5s-fre: Provides French language-specific string resources for fēnix 5 devices only
  • Thanks, but that's not really relevant to my situation!

    I'm not trying to select based on device, I'm trying to allow users to select based on their preference...

    For example, I have a number of DataField apps where users may choose to display one of thirty+ metrics...

    The coding difference between each metric is simply the choice of property I read from Activity.Info.

    So I need to be able to change between reading info[:currentSpeed] or info[:currentCadence] programatically, based on a user setting.

    There is no way (that I am aware of) to include symbols as options in the settings files, so I need to convert an integer to a symbol at run time.

  • How about this:  

    The xml:

    <resources>
        <jsonData id="K0">[1]</jsonData>
        <jsonData id="K1">[10]</jsonData>
        <jsonData id="K2">[100]</jsonData>
    </resources>

    Then an array of keys to access things:

    var res=[Rez.JsonData.K0, Rez.JsonData.K1, Rez.JsonData.K2];
            
            for(var i=0;i<res.size();i++) {
            	System.println("key "+i+"="+WatchUi.loadResource(res[i]));
            }

  • Isn't that essentially what I am doing already?

    function getResource(key) {

    return WatchUi.loadResource([Rez.JsonData.K0, Rez.JsonData.K1, Rez.JsonData.K2, ... ][key]);

    }

    I have an array of (hardcoded) keys that I select using an index.

    But what I'm trying to do is to get away from the need for the hardcoded array as... well... if you have 30 or more hardcoded Rez identifiers a) it is ugly, b) it is hard to maintain and c) it uses up a lot of memory.

  • I've never seen a way to convert a sting into a symbol 

    The array of symbols isn't really a big impact on memory

  • "The array of symbols isn't really a big impact on memory"

    I'm looking at several, long lists of symbols / resource identifiers that equate to more than 2KB of runtime memory when compiled...

    Obviously, I'm going to reengineer to remove that, but if I had something as simple as a way to evaluate a symbol or resource identifier from a setting, it would be a non-issue and I wouldn't have to reengineer.

  • If you have enough memory in your app then you could try having just one JsonData item which is an array of items - you can then load in the whole array (e.g. at app startup when you are mostly likely to have free memory). Use your key to index the array and keep a reference to the single element in the array of items you want. And then set all other elements of the array to NULL so that their memory gets released.

    This temporarily uses more memory, but you no longer need to keep a permanent array of the JsonData Rez references.

    If you do still need an array of multiple Rez references it can help memory slightly to do this in a function:

    var jsonData = Rez.JsonData;
    var loadPreset = [jsonData.id_preset0, jsonData.id_preset1, jsonData.id_preset2, etc];

  • The syntax you propose in your first snippet will work just fine provided that the key is a Symbol.

    using Toybox.Application;
    using Toybox.Lang;
    using Toybox.System;
    using Toybox.WatchUi;
    
    const json_symbols = [
        :options1,
        :options2,
        :options3,
        :options4,
        :options5
    ];
    
    class SymbolApp extends Application.AppBase {
    
        function initialize() {
            AppBase.initialize();
    
            for (var i = 0; i < json_symbols.size(); ++i) {
                var symbol = json_symbols[i];
    
                var data = WatchUi.loadResource(Rez.JsonData[symbol]);
    
                System.println(Lang.format("$1$ => $2$ => $3$", [ i, symbol, data ]));
            }
    
        }
    
        function getInitialView() {
            return [ new WatchUi.View() ];
        }
    
    }

    <resources>
        <jsonData id="options1" filename="options1.json"/>
        <jsonData id="options2" filename="options2.json"/>
        <jsonData id="options3" filename="options3.json"/>
        <jsonData id="options4" filename="options4.json"/>
        <jsonData id="options5" filename="options5.json"/>
    </resources>

    The problem that you're running into is that you seem to want to be able to convert a Setting value (a String or Number) to a Symbol so that you can load the corresponding JSON resource. We don't allow this, and it is by design.

    You say that the mapping Array (from index to Symbol) is 2K in memory. If I remember correctly, the memory used by each value in the array is 5 bytes. In the worst case it would be rounded up to 8 bytes, and then there is a bit of overhead for the Array itself. Even at 8 bytes per, you'd have ~256 elements in that Array? Is that right?

  • Ok, let me give a concrete example.

    The difference in runtime memory is slightly shy of 2KB - with compiled arrays, it gives 12.1kb, with empty arrays, it gives 10.3KB. Both examples are simply the default "Complex DataField" template project with methods included as per the below, with a compiled json resource file as per the last entry. 

    Code 1, empty arrays:

    function findResource(key) {
     return [][key];
    }
    function interpretProperty(key) {
     return [][key];
    }

    Code 2, filled arrays:

    function findResource(key) {
     return [
      Rez.JsonData.DISPLAY_NONE,
      Rez.JsonData.DISPLAY0,
      Rez.JsonData.DISPLAY1,
      Rez.JsonData.DISPLAY2,
      Rez.JsonData.DISPLAY3,
      Rez.JsonData.DISPLAY4,
      Rez.JsonData.DISPLAY5,
      Rez.JsonData.DISPLAY6,
      Rez.JsonData.DISPLAY7,
      Rez.JsonData.DISPLAY8,
      Rez.JsonData.DISPLAY9,
      Rez.JsonData.DISPLAY10,
      Rez.JsonData.DISPLAY11,
      Rez.JsonData.DISPLAY12,
      Rez.JsonData.DISPLAY13,
      Rez.JsonData.DISPLAY14,
      Rez.JsonData.DISPLAY15,
      Rez.JsonData.DISPLAY16,
      Rez.JsonData.DISPLAY17,
      Rez.JsonData.DISPLAY18,
      Rez.JsonData.DISPLAY19,
      Rez.JsonData.DISPLAY20,
      Rez.JsonData.DISPLAY21,
      Rez.JsonData.DISPLAY22,
      Rez.JsonData.DISPLAY23,
      Rez.JsonData.DISPLAY24,
      Rez.JsonData.DISPLAY25,
      Rez.JsonData.DISPLAY26,
      Rez.JsonData.DISPLAY27,
      Rez.JsonData.DISPLAY28
     ][key];
    }
    function interpretProperty(key) {
     return [
      :altitude,
      :ambientPressure,
      :averageCadence,
      :averageDistance,
      :averageHeartRate,
      :averagePower,
      :averageSpeed,
      :bearing,
      :bearingFromStart,
      :calories,
      :currentCadence,
      :currentHeading,
      :currentHeartRate,
      :currentLocation,
      :currentLocationAccuracy,
      :currentPower,
      :currentSpeed,
      :distanceToDestination,
      :distanceToNextPoint,
      :elapsedDistance,
      :elapsedTime,
      :elevationAtDistance,
      :elevationAtNextPoint,
      :energyExpenditure,
      :frontDerailleurIndex,
      :frontDerailleurMax,
      :frontDerailleurSIze,
      :maxCadence,
      :maxHeartRate,
      :maxPower,
      :maxSpeed,
      :meanSeaLevelPressure,
      :nameOfDestination,
      :nameOfNextPoint,
      :offCourseDistance,
      :rawAmbientPressure,
      :rearDerailleurIndex,
      :rearDerailleurMax,
      :rearDerailleurSize,
      :startLocation,
      :startTime,
      :swimStrokeType,
      :swimSwolf,
      :timerState,
      :timerTime,
      :totalAscent,
      :totalDescent,
      :track,
      :trainingEffect,
      :activeMinutesDay,
      :activeMinutesWeek,
      :activeMinutesWeekGoal,
      :floorsClimbed,
      :floorsClimbedGoal,
      :floorsDescended,
      :metersClimbed,
      :metersDescended,
      :moveBarLevel,
      :stepGoal,
      :steps,
      :cadence,
      :groundContactBalance,
      :groundContactTime,
      :stanceTime,
      :stepCount,
      :stepLength,
      :verticalOscillation,
      :verticalRatio,
      :walkingFlag,
      :dst,
      :hour,
      :min,
      :sec,
      :timeZoneOffset
      ][key];
    }

    The Json resource is this:

    <jsonDataResources>
      <jsonData id="DISPLAY_NONE">[-1,1,"NOT SET"]</jsonData>
      <jsonData id="DISPLAY0">[0,1,"CADENCE"]</jsonData>
      <jsonData id="DISPLAY1">[1,1,"MPS"]</jsonData>
      <jsonData id="DISPLAY2">[1,2.23694,"MPH"]</jsonData>
      <jsonData id="DISPLAY3">[1,3.6,"KPH"]</jsonData>
      <jsonData id="DISPLAY4">[2,1.524,"PACE /100y"]</jsonData>
      <jsonData id="DISPLAY5">[2,1.666666667,"PACE /100m"]</jsonData>
      <jsonData id="DISPLAY6">[2,16.66666667,"PACE /km"]</jsonData>
      <jsonData id="DISPLAY7">[2,26.82233333,"PACE /mile"]</jsonData>
      <jsonData id="DISPLAY8">[2,53.64466667,"PACE /2mile"]</jsonData>
      <jsonData id="DISPLAY9">[2,83.33333333,"PACE /5km"]</jsonData>
      <jsonData id="DISPLAY10">[2,134.1116667,"PACE /5mile"]</jsonData>
      <jsonData id="DISPLAY11">[2,166.6666667,"PACE /10km"]</jsonData>
      <jsonData id="DISPLAY12">[2,351.625,"PACE /half-marathon"]</jsonData>
      <jsonData id="DISPLAY13">[2,703.25,"PACE /marathon"]</jsonData>
      <jsonData id="DISPLAY14">[3,1,"HEARTRATE"]</jsonData>
      <jsonData id="DISPLAY15">[4,1,"POWER"]</jsonData>
      <jsonData id="DISPLAY16">[5,1,"CALS"]</jsonData>
      <jsonData id="DISPLAY17">[6,-1,"METS"]</jsonData>
      <jsonData id="DISPLAY18">[7,1,"MOD MINS DAY"]</jsonData>
      <jsonData id="DISPLAY19">[8,1,"VIG MINS DAY"]</jsonData>
      <jsonData id="DISPLAY20">[9,1,"TOT MINS DAY"]</jsonData>
      <jsonData id="DISPLAY21">[13,1,"STEPS"]</jsonData>
      <jsonData id="DISPLAY22">[14,1,"RD GCB"]</jsonData>
      <jsonData id="DISPLAY23">[15,1,"RD GCT"]</jsonData>
      <jsonData id="DISPLAY24">[16,1,"RD ST"]</jsonData>
      <jsonData id="DISPLAY25">[17,1,"RD Slen"]</jsonData>
      <jsonData id="DISPLAY26">[18,1,"RD VO"]</jsonData>
      <jsonData id="DISPLAY27">[19,1,"RD VR"]</jsonData>
      <jsonData id="DISPLAY28">[20,1,"RD Walk"]</jsonData>
    </jsonDataResources>

    The result in terms of compiled, runtime memory, as reported by the memory tool is this:

    Memory consumptionn compared