Here are a few Complication samples

Hey folks, the Complication examples in the Core Topics documentation doesn't really help you build an end-to-end demo.

So, with the help of others here, I've prepped two Complication samples for System 6 devices that;

  1. Subscribe to Complication callbacks, and display their data in a watchface, and
  2. Pressing them within a bounding box (onPress) to launch the Complication widget.

This code deals with square press bounds;
https://github.com/sunpazed/garmin-complicate

While this one deals with round press bounds;
https://github.com/sunpazed/garmin-complicate-circle

Pick whichever works for your design or approach.

Here's a video of what the "onPress" does with one of my watchfaces;
https://www.youtube.com/watch?v=Gp-m-2xvfM0

The "onPress" doesn't do anything yet, but hopefully we'll see this change with a new SDK or firmware update in the future.

Happy to hear any feedback, and please contribute / improve these code samples for everyone.

  • Another question, I noticed in the sample that the type checking was set to 0. Without that in the jungle file, the sample throws a compile error.  I noticed in the API docs there is a ComplicationChangedCallback typedef.  I could not immediately find however a good example of how to use this typedef with a callback function.  How do you do that?  Given the unfamiliar to me syntax for referencing the callback I'm not even sure how one would do something simple like say just cast it to the right type?

    I wouldn't use type casting in Monkey C period, unless I absolutely couldn't avoid it. Much like typescript is a compile-time layer on top of JavaScript, Monkey Types is a compile-time layer on top of Monkey C. Monkey Types, the type checker, and type casting exist only at compile-time, meaning there's no run-time need or benefit to casting (unlike Java or C#). The only point to casting in Monkey C is to get around some limitation of the type system. The downside of casting in Monkey C is that it bypasses the type checker and enables you to write code that may cause a run-time type error.

    (Unfortunately, there are currently some situations in Monkey C which do require casting if type checking is enabled, but this isn't one of them.)

    Here's the code that I think you're talking about:

    https://github.com/sunpazed/garmin-complicate/blob/17b305c0c702911a3bd3777a91bb7e60aca8d3de/src/WatchApp.mc#L18-L36

        // register all the complication callbacks
        function onStart(state) {
          Complications.registerComplicationChangeCallback(self.method(:onComplicationUpdated));
          Complications.subscribeToUpdates(new Complications.Id(Complications.COMPLICATION_TYPE_HEART_RATE));
          Complications.subscribeToUpdates(new Complications.Id(Complications.COMPLICATION_TYPE_STEPS));
          Complications.subscribeToUpdates(new Complications.Id(Complications.COMPLICATION_TYPE_CURRENT_TEMPERATURE));
          Complications.subscribeToUpdates(new Complications.Id(Complications.COMPLICATION_TYPE_BODY_BATTERY));
        }
    
        // fetches the complication when it changes, and passes to the Watchface
        function onComplicationUpdated(complicationId) {
            if (WatchView != null) {
                try {
                    WatchView.updateComplication(complicationId);
                } catch (e) {
                    Sys.println("error passing complicaiton to watchface");
                }
            }
        }
    

    Here's the typedef you referenced:

    ComplicationChangedCallback as Lang.Method(id as Complications.Id) as Void

    The related type check error is:

    ERROR: fenix7system6preview: .../garmin-complicate/src/WatchApp.mc:20,6: Invalid '$.Toybox.Lang.Method(complicationId as Any) as Any' passed as parameter 1 of type 'PolyType<Null or $.Toybox.Complications.ComplicationChangedCallback>'.

    To fix this error, onComplicationUpdated() needs to be explicitly typed (the types of function argument(s) and return value must be specified).

    Before:
    function onComplicationUpdated(complicationId) {

    After:
    function onComplicationUpdated(complicationId as Complications.Id) as Void {

    Here's the complete set of changes I made to get the project to compile without errors, using the default type checking level. (There are still warnings, though.)

    1. Commented out the "project.typecheck" line in monkey.jungle
    2. Added the ComplicationSubscriber permission to manifest.xml
    3. Changed onComplicationUpdated() as described above

    [https://github.com/flowstatedev/garmin-complicate/commit/b7cded0df884f43fbba3ecb0933befd0e283234e]

    EDIT: I want to emphasize that this code builds without errors using the default type checking level in the latest SDK (as well as earlier SDKs), and runs in the simulator. Furthermore, I directly answered a specific question: how to get the complication change callback function to work with type checking enabled, for that specific project.

    If similar code doesn't work in a different project, that's neither here nor there. I do agree that "import" is preferred over "using" for new projects.

    Why do I care what others say in response to this comment? it's super annoying to put a bunch of work into updating code so it builds without error and crafting a detailed response, only to be incorrectly (and condescendingly) told that it's wrong. e.g. "if you look at the github referenced", as if I didn't fork it, mod it and build it for the sole purpose of trying to answer a question on the forums.

  • If you look at the sample I linked to, in the View's initialize(), I have 

            //check for complications
            hasComplications=(Toybox has :Complications); 
            if(hasComplications) {
                hrId=new Id(Complications.COMPLICATION_TYPE_HEART_RATE);
                if(hrId!=null) {
                    hrComp = Complications.getComplication(hrId);
                    Complications.registerComplicationChangeCallback(self.method(:onComplicationChanged));
                    Complications.subscribeToUpdates(hrId);        
                }
            }

    And here's the callback:

        function onComplicationChanged(id as Complications.Id) as Void {
            if(id.equals(hrId)) {
                curHr=Complications.getComplication(id).value;
            }
    
        }

    I have

    import Toybox.Complications;
    if I comment out that import, type checking throws an error. 
    ERROR: fr965: C:\Users\James\workspace-prod\ComplexWF\source\ComplexWFView.mc:204: Cannot resolve type 'Complications.Id'.
    With it, no problem with type checking on
    If you look at the github referenced, it's not using import, but using "using".  It wasn't written for type checking being on
    using Toybox.Complications as Complications;


    If I change the import in my code to "using", I get this error:

    ERROR: fr965: C:\Users\James\workspace-prod\ComplexWF\source\ComplexWFView.mc:86,12: Cannot find symbol ':Id' on type 'self'.
  • I have

    import Toybox.Complications;
    if I comment out that import, type checking throws an error. 
    ERROR: fr965: C:\Users\James\workspace-prod\ComplexWF\source\ComplexWFView.mc:204: Cannot resolve type 'Complications.Id'.
    With it, no problem with type checking on

    That's not a type checking error, as simply commenting out "import Toybox.Complications" (without adding the corresponding using statement) will cause that error whether or not type checking is enabled. That's just a classic name resolution error.

    If I go back to SDK 3.2.5 (which does not include type checking or import), and try to build the following code without using Toybox.Time...

    var t = new Time.Duration(30);

    ...I get the following error:

    Unable to detect scope 'Time.Duration' for the symbol 'initialize'.

    If I do the same thing with a newer SDK that has type checking (even if type checking is disabled), the following error is returned:

    Cannot resolve type 'Duration'.

    Yeah, the wording of the error message changed and it has the word "type" in it, so it's natural to assume that it's literally a type checker issue, but actually it existed before type checking was added to CIQ, and it exists even when type checking is disabled.

    Monkey C has always required modules to be included with using or import before you can use the associated symbols, except maybe for certain modules which are implicitly included, like Toybox.Lang.


    If I change the import in my code to "using", I get this error:

    ERROR: fr965: C:\Users\James\workspace-prod\ComplexWF\source\ComplexWFView.mc:86,12: Cannot find symbol ':Id' on type 'self'.

    That's because of the following code which references the Id type without the Complications module prefix:

    hrId=new Id(Complications.COMPLICATION_TYPE_HEART_RATE);

    If you look at the github repo that I was discussing with the other poster, there are no type references without module names. All the type references are fully qualified, which is why import is unnecessary.

    I can make one very simple change to your code to get it to compile with "using Toybox.Complications" instead of "import Toybox.Complications":

    hrId=new Complications.Id(Complications.COMPLICATION_TYPE_HEART_RATE);

    You see, there are two differences between "using" and "import", both of which are documented.

    - using allows defining an alias for a module with as, import does not

    - import allows type names to be used without the module name (e.g. Lang or Complications), using (usually) does not.

    To be fair, there is a strange quirk where new Id(...) will compile and run with using Toybox.Complications, but only when type checking is disabled, so I can see how that would lead one to the conclusion that using doesn't work with type checking. However, that doesn't change the fact code can have using statements with the type checker enabled, as long as types are fully qualified.

    All of this is why I said it doesn't make sense for one person to post code and another person to use different code to explain why the original code is wrong. The original code had an issue causing a type check error which had *nothing* to do with using vs import.

    I wouldn't have used the tone I did if the reply wasn't both condescending and incorrect. Sorry, but if someone is going to talk down consistently, it would really help to be sure they know what they are talking about. It's not just about how I feel about the reply, it's also the fact that it's a form of misinformation. I'm a big fan of trying to understand how and why things actually work, not just relying on received wisdom and blanket statements.

    Of course we can agree that Garmin seemingly prefers import over using now. Doesn't mean that all code which still references using cannot be compiled with type checking enabled.

  • Hey folks Wave I’m glad these examples kicked off this conversation, I really love this community!

    It has highlighted how buggy my initial code was Stuck out tongue winking eye

    I do have an updated and more complete example I’ll post on GitHub shortly (ie; next few weeks). Should address the bugs, exception capturing, and unit conversion issues.