Design pattern for foreground/glance/background separation?

I have some classes that have functions shared between background and foreground, and others are used only in foreground. I am looking for a good design pattern to split the two.

Here is an example:

class A {
    private var _b as B;
    
    function initialize() {
        // some complicated logic
        _b = new B();
        // some complicated logic
    }
    
    function neededInBackground() as Void {
        _b.neededInBackground();
    }

    function notNeededInBackground() as Void {
        _b.notNeededInBackground();
    }
}

class B {
    function neededInBackground() as Void {}
    function notNeededInBackground() as Void {}
}

Let's say the logic in A.initialize() is so complicated that I do not want to duplicate it. But when in foreground, it should instantiate the foreground class, when in background, the background class. Also the instantiation can happen in different places, I don't want to just everywhere have if else statements everywhere I instantiate that class.

Best thing I could come up with as solution is this:

class Instantiator {
    static function instantiateB() as B_BG {
        if( EvccApp.isBackground ) {
            return new B_BG();
        } else {
            return new B_FG();
        }
    }
}

class A_FG extends A_BG {
    function initialize() {
        A_BG.initialize();
    }

    function getB_FG() as B_FG {
        return _b as B_FG;
    }
    
    function notNeededInBackground() as Void {
        getB_FG().notNeededInBackground();
    }
}

class A_BG {
    protected var _b as B_BG;
    
    function initialize() {
        // some complicated logic
        _b = Instantiator.instantiateB();
        // some complicated logic
    }
    
    function neededInBackground() as Void {
        _b.neededInBackground();
    }
}


class B_FG extends B_BG {
    function initialize() {
        B_BG.initialize();
    }
    function notNeededInBackground() as Void {}
}

class B_BG {
    function neededInBackground() as Void {}
}

What do you think? Is this the way? Or is there something easier/better to scope the code?

Top Replies

All Replies

  • When a prg gets built, it starts with a header, then the code that's annotated with (:background), then the code annotated with (:glance), then everything else.

    So when the background runs, only the code that has the (:background) annotation gets loaded.  If you are showing the glance, only the code with (:background) or (:glance) gets loaded, meaning in a glance, all the code for the background or glance is available.  When the main app runs, the everything is available - background, glance and everything else is available.

    You need to use the annotations, or when, for example, the background runs, but it will try to load the entire app, and likely run out of memory, as the memory available for the background won't be enough to load the full prg.

    You aren't showing any annotations in the code you posted.

    See https://developer.garmin.com/connect-iq/connect-iq-faq/how-do-i-create-a-connect-iq-background-service/#howdoicreateaconnectiqbackgroundservice

    It was written before glances, so it doesn't address those, but here's something that does and talks about the :glance annotation

    https://developer.garmin.com/connect-iq/core-topics/glances/

  • Yes, I understand the annotations, I forgot to include them in the example.

    Unfortunately I cannot edit it anymore, just getting the eternal three dots ...

    Anyway, in the first example it would be A/B that have :background, then in the second the Instantiator, A_BG and B_BG.

  • I don't think it will compile, because part of your background code (Instantiator) references code that is not annotated with : background. 

  • Here's a ultra simple background service so you can see the basics

    import Toybox.Background;
    import Toybox.System;

    (:background)
    class ComplexWFServiceDelegate extends Toybox.System.ServiceDelegate {
       
        function initialize() {
            System.ServiceDelegate.initialize();
        }
       
        function onTemporalEvent() {
            var now=System.getClockTime();
            now=now.hour+":"+now.min.format("%02d");
            Background.exit(now);
        }
    }
    in your AppBase you need this so it can find your delegate
        function getServiceDelegate(){
            return [new ComplexWFServiceDelegate()];
        }
    And you need to do the register lsomething ike this (I do it along with getInitialView)
    Background.registerForTemporalEvent(new Time.Duration(5 * 60));
    And then, have code in onBackgroundData to handle the data from the background service.
    I'm really not sure how the code you posted would be used, even with annotations.
  • I don't think it will compile, because part of your background code (Instantiator) references code that is not annotated with : background. 

    Yes and no.

    - At the default type check level (1 / gradual), there are no warnings or errors related to that kind of problem

    - At type check level = 2 / informative, that kind of problem is a warning: ("X not available in all function scopes")

    - At type check level = 3 / strict, that kind of problem is an error (same as above)

    The docs aren't quite right in this regard:

    [https://developer.garmin.com/connect-iq/core-topics/backgrounding/]

    If your type check level is at gradual or above, the compiler will detect if your background service or any objects referenced is attempting to reference something not marked as background.

    This can be worked around by using the :typecheck(disableBackgroundCheck) annotation on any function that is available to the background but tries to access a variable/function that's only in the foreground.

    i.e.

    (:typecheck(disableBackgroundCheck))
    static function instantiateB() as B_BG {
    // ...

    As a side note, it isn't possible to have part of a class in the background, and part of it not in the background. e.g. if only 1 function of a class is annotated with :background, then the entire class will be in the background.

    I know that nobody suggested otherwise, but just preemptively bringing it up, since it has come up before and it's not documented.

  • I would modify the 2nd code snippet in a couple of ways:

    - change B_BG to implement notNeededInBackground() as an empty function, to avoid the cast in getB_FG(). btw, having a whole separate function for the cast is a waste (and somewhat misleading), since casts have zero effect at runtime. (Monkey Types is compile-time only, just like TypeScript). The effect of a compile-time cast "value as T" in Monkey C is only to tell the type checker that value is of type T, not to actually make it true at runtime. There are no run-time casts, so the closest you could get is to use instanceof to verify that a value is of a given type at runtime (ofc that would also be a waste, in this case).

    - get rid of Instantiator and move its functionality to a static function on B_BG

    - ideally, I would've liked to make the constructors for B_BG and B_FG protected, but then this prevents them from being called by anything other than a non-static member function of their own class. Instead, I would add documentation about not directly instantiating B_BG or B_FG via new. I might also add some dummy args with suggestive names to each constructor, so it would be impossible to "accidentally" use "new B_BG()" or "new B_FG()", and if you tried, the IDE would show you the named args as you type.

    The following code compiles without warnings or errors with typecheck level 3 (strict).

    https://pastebin.com/2KKjWmFT

  • Thanks for the suggestions, I like your implementation!

    change B_BG to implement notNeededInBackground() as an empty function, to avoid the cast in getB_FG().

    In my case I'd avoid that, just because there are a few of these functions, and the stubs would take a bit of memory again.

    ideally, I would've liked to make the constructors for B_BG and B_FG protected, but then this prevents them from being called by anything other than a non-static member function of their own class.

    I am wondering about that. Why can I call a private constructor from a static member function, but not a protected one?

    This does works:

    class B_X {
        public static function getInstance() as B_X {
            return new B_X();
        }
        private function initialize() {}
    }

    This does not:

    class B_X {
        public static function getInstance() as B_X {
            return new B_X();
        }
        protected function initialize() {}
    }
  • And what is the effect of adding that annotation? Does it compile but fail at runtime? Does it add the whole class referenced to the background code?

  • And what is the effect of adding that annotation?

    :typecheck(disableBackgroundCheck) does simply what it says. It disables the background scope check that's performed by the type checker, which was already described above, when the annotation was first mentioned.

    In other words, it tells the type checker not to verify that the annotated function/class/module does not access values that are not available to the background. (Ofc, if the annotated item is not in the background, then this annotation has no effect.)

    You should understand that since it was suggested by the CIQ team in your own bug report that I linked above.

    https://forums.garmin.com/developer/connect-iq/i/bug-reports/bug-compiler-thinks-all-functions-are-background-if-one-function-in-the-class-is-annotated-as-such

    Does it compile but fail at runtime?

    Obviously it's up to the implementer to ensure that it doesn't fail at runtime.

    As written, the code in question won't fail at runtime as long as the runtime evcc.isBackground flag is set correctly and/or isBackground() returns the correct value.

    Or as the docs put it:

    https://developer.garmin.com/connect-iq/monkey-c/monkey-types/

    Application Scope Type Checking

    The type checker attempts to validate that any member fetched from a module or class is available in all of the same application scopes as the caller. If the developer is confident their code is application scope safe and the type checker still complains, this check can be disabled for background or glance scopes using the annotations :typecheck(disableBackgroundCheck) or :typecheck(disableGlanceCheck), respectively. To disable checks for both background and glance scopes, use the annotation :typecheck([disableBackgroundCheck, disableGlanceCheck]).

    --

    Does it add the whole class referenced to the background code?

    No, and I wouldn't expect it to, either. Ofc that it is something you could've verified for yourself.

  • I am wondering about that. Why can I call a private constructor from a static member function, but not a protected one?

    Hmmm good question. Maybe something to do with the fact that a private method can't be overridden?

    Ofc there's nothing preventing the same pattern from working in Java, where you can have a protected constructor and a static method that calls the constructor.

    My guess is that it's just a quirk of the monkey c implementation as opposed to anything intentional, but who knows.