Calling function from another class causes type error

Hello guys! I have an issue to call function from another class. I know I'm doing something wrong here but I have no clue what.. so, any advises would be highly appreciated. I have this class called "Tools" where I'm trying to store functions needed often. However when calling the function to load settings simulator crashes with error "Error: Unexpected Type Error"

import Toybox.Graphics;
import Toybox.WatchUi;
import Toybox.Application;
import Toybox.Lang;
using Toybox.Time.Gregorian;
  
var toolsInstance;

class Tools {
    
    function initialize() {
        toolsInstance = new Tools();
    }

    public function medNames() as Array<String> {
        // fetch settings
        var medNames = new Array<String>[15];        
        for (var i=0; i <= 14; i++) {
        medNames[i]= Properties.getValue("MedName"+i).toString();
        }
        System.println("Do we even come here?");
        return medNames;
    }
}   

This is how I try to call it from another class:

var medNames = new Array<String>[15];
medNames = toolsInstance.medNames();
Many thanks in advance!
  • 1. you don't need to create an array to declare a variable. If medNames is a local variable then:

    var medNames = toolsInstance.medNames();

    or if it's a class variable, then:

    var medNames as Array<String>;

    2. on which line you get the error?

    Try: Properties.getValue("MedName"+i) as String;

  • 2. on which line you get the error?

    Try: Properties.getValue("MedName"+i) as String;

    But OP is getting a *runtime* error (Unexpected Type Error), and a type cast ("as String") won't prevent that from happening, since as we all know, Monkey Types is only a compile-time type checking system. IOW, an "as String" type cast simply tells the type checker "this is a string", it doesn't necessarily make it so at runtime.

    It seems to me that the most likely scenario is that getValue() is returning null, so the answer would be to check the return value of Properties.getValue() for null and take appropriate action:

    for (var i=0; i <= 14; i++) {
      var name = Properties.getValue("MedName" + i);
      if (name == null) {
        medNames[i] = ""; // or null or perhaps "N/A". it depends on how medNames is used elsewhere
      } else {
        medNames[i] = name.toString();
        // calling toString() is superfluous here if the property is already expected to be a string.
        // however if it's supposed to be a Number, Float, Long, Double or Boolean,
        // then toString() would be appropriate. doesn't seem likely due to the prefix "MedName" tho
      }
    }

  • Thanks guys! I did some changes (added type for Properties, made function calling simpler and also added null checking for properties). The app still crashes. Hmmm.. what am I missing.. 

    Now I'll call the function like this:

    var medNames2 = tools2.medNames();
    

    and the class is like below:

    import Toybox.Graphics;
    import Toybox.WatchUi;
    import Toybox.Application;
    import Toybox.Lang;
    using Toybox.Time.Gregorian;
    import Toybox.System;
        
    var tools2;
    
    class Tools2 {
        
        function initialize() {
            tools2 = new Tools2();
        }
    
        public function medNames() as Array<String> {
            // fetch settings
            var medNames = new Array<String>[15];        
            for (var i=0; i <= 14; i++) {
                var name = Properties.getValue("MedName"+i);
                if (name == null) {
                    medNames[i] = "--";
                }   
                else {
                    medNames[i]= name;
                }
            }
    
            System.println("Do we even come here?");
            return medNames;
        
        }   
    }

    The app crashes when calling the function, stack trace:

    Error: Unexpected Type Error
    Details: Failed invoking <symbol>
    Stack: 
      - onUpdate() at /home/user/Garmin//MedTrac/source/MedTracView.mc:49 0x100042d7 
    
    Encountered app crash.

    The line 49 is the call for function:

    var medNames2 = tools2.medNames();
    

  • Tools2.initialize() won't be called unless a new Tools2 object instance is created using the new keyword. The initialize() function of a Monkey C class is similar to object constructors in other languages.

    (See: [https://en.wikipedia.org/wiki/Constructor_(object-oriented_programming))])

    e.g.

    var t = new Tools2();

    But in that case, you now have a Tools2 object instance (in the above example, t is a Tools2 instance after the code runs), so there's no point in creating a new Tools2 object and assigning it to a a global variable.

    As a matter of fact, your code as written will cause infinite recursion if new Tools2() is ever invoked, leading to a stack overflow crash, as Tools2.initialize() is called by new Tools2() and also calls new Tools2();

    The proper way to handle this would be to have an empty initialize() function in Tools2.

    In your View class (or wherever else you need Tools2), you would call new Tools2(), store the result in a member variable, then use that member variable to access whatever you need from Tools2.

    e.g.

    class MyView ... {
      var tools2;
      function initialize() {
        //...

        tools2 = new Tools2();
        //...

      }

      function onUpdate() {
        //...

        var medNames2 = tools2.medNames();
        //...

      }
    }

    However, in this case Tools2 doesn't have any internal state, so instead of being a class, it could be a module. The advantages of this approach would be that the code is simpler and you save memory (both for code and data), since you wouldn't need to create an instance of Tools2.

    e.g.

    module Tools2 {
      public function medNames as Array<String> {
      // ...
      }
    }

    You would use this module as follows.

    var medNames2 = Tools2.medNames();

    To save even more memory, you could just forgo the module altogether and have medNames as a global function. Unlike global variables, there isn't much downside to global functions for this kind of use case, other than the fact that modules are better for organizing your code. But I don't think it's a big deal for small, memory-constrained apps that aren't shared publicly (as opposed to the CIQ API or a public library.)

  • Understood, thanks a lot for clear explanation. Now I understand what was wrong in my code. Converted that to module and works like a charm. Meanwhile I already tried to use that as a global function, but having module structure there indeed makes it more organized. Thank you all very much for help!