How to dynamically access a class field

I want a DataField that can present a variety of different data values, with a settings page to choose which one.

One way to do this would be to have a member identifying which field via an enum, and switch on the enum to extract the value. But it seems like you should be able to just store the symbol for the field. Something like:

class MyView extends WatchUi.DataField {
hidden var mInfoField as Symbol = :currentSpeed; // for example

function computeValue(info as Activity.Info) as Void {
if (info has mInfoField) {
var field = info[mInfoField]; // <-- warning is here
if (field != null) {
var value = field.toFloat();
...
}
}
}
...
}
This appears to work perfectly at runtime, but results in a warning at compile time:
WARNING: fenix5xplus: MyView.mc:31: Cannot determine if container access is using container type.
Is there a clean way to do this (or to suppress this warning, on this one line)? It feels weird that you can determine whether the field exists dynamically (info has mInfoField), but then not be able to get its value (without raising a warning at compile time). For methods, there's the explicit method(m) to do this, but I can't find a corresponding way to fetch a value given its symbol.
  • Note.  You can not use symbols with Storage or Properties. So you want to use a Number to configure what you want to display.

    If you insert your code as "code", it would be clear what line 31 is.

    From the API doc:

    Note:

    Symbols can change from build to build and are not to be used for for Keys or Values

  • Sorry, I tried to indicate where the warning was in the comments. It's complaining about info[mInfoField], where info is an Activity.Info.

    My point is that I'm allowed to say "info has mInfoField"... but its not clear how I should then get the value.

    I'm trying to do something like:

      enum { CURRENT_SPEED, CURRENT_HEARTRATE, /* etc */ };

      ...

      switch (field) {

        case CURRENT_SPEED:

            return info has :currentSpeed) ? info.currentSpeed : null;

        case CURRENT_HEARTRATE:

            return info has :currentHeartRate ? info.currentHeartRate : null;

        // etc

      }

    But without the extra layer of indirection. It's an object of a known class, it has the field I'm interested in... shouldn't I be able to get its value?

    And yes, I know I can't persist a Symbol. The idea is to have something like:

    [

     [ :currentSpeed, "Speed", :speedFormatter ],

     [ :currentHeartRate, "HR", :numberFormatter ],

     // etc

    ]

    and store the index into that array.

  • You're making things too complex by trying to use symbols, and as I said, you can't use symbols in app settings to configure what data to display anyway.

  • I guess we'll have to disagree over what's more complicated. One version does info[mInfoField], no matter how many different fields I want to be able to access; the other has a big switch statement, with one case for each field I want to access (and where each case is basically identical, except for the choice of symbol).

    And as I explained above, I don't use the symbol in the app settings per se; I just select it from an Array, based on an index thats stored in the settings.

    I guess I'll settle for what I have since its working, and (imo) much simpler, even though it causes a warning. If it ever starts failing (eg if a future version of monkey c stops supporting this idiom), I'll have to re-assess.

  • The thing is you'll still probably need if() else or case statements.

    Consider speed and distance.  For each, you'll have to do the proper conversion using pace/speed units and distance distance units.  HR there is no conversion, etc. 

    It's not just getting the data but also doing any conversions needed.

  • I was trying to show that thats taken care of (:speedFormatter, :numberFormatter above). We're starting to wade too far into the details - but the way its actually coded, there's a method to get the value, a method to transform the value, and a method to format the result. Keeping them separate, rather than scattering the transform/format code throughout the switch statement, makes it easier to test, easier to maintain, and reduces redundancy. The separation also makes it trivial to convert an "average-X" value into an "average-X-for-this-lap" value, or (slightly harder) "average-X-over-last-N-seconds".

    So the choice is literally between a single line of code, or an n-case switch statement together with an n-element enum. The latter is both wasteful in code size, and harder to maintain.

    I'll also note that this all works, and has been working for a while - the only issue is that when I turned on type checking, I started getting that warning, and can't find a way to suppress it.

    And I've been trying to avoid all this justification, because the question seems *really* simple:

    Having determined that a field exists (obj has field) is there a type safe way to get the value of the field?

  • I've just noticed that the release notes for 4.0.7 say:

     - Add type checking to ‘object[:variable]’ syntax

    So I've submitted a bug report