Exit app on select button

Good morning,

I'd like to have my widget exit on press of the select button. So basically the select button should do the same as the back button. I could not find a method to either exit the app or trigger a back button event. Am I missing something, or is this not possible?

Thanks for your help!

  • You can always exit a device app or widget by popping the top-level view. For device apps, you can call System.exit() at any point (although I suppose you will have problems if you don’t clean up certain things, like any active activity recording.)

    Calling System.exit() on a true widget (for older devices that don’t support super apps *) will cause the app to crash, so it’s not recommended. There’s been a lot of discussion about why this should be the case, and it boils down to the fact that the firmware “wouldn’t know what to do” when you call System.exit(). Personally I think it should just do the equivalent of popping the top-level view but whatever.

    (* Super apps are basically the merging of device apps and widgets, for all CIQ 4+ devices. When either a device app or widget is built for a CIQ 4+ device, it’s actually internally labelled as a device app, and it’s always available from the activity/app launcher like all device apps. If the developer chooses to implement a glance view, it’s also available from the glance loop. Note the difference from true widgets, where a default glance view would be provided if none was implemented.)

    I haven’t seen what happens when System.exit() is called on a super app that was launched from a glance. My guess is that it should work fine, but maybe somebody can confirm this.

  • Calling System.exit() on a true widget (for older devices that don’t support super apps *) will cause the app to crash, so it’s not recommended. There’s been a lot of discussion about why this should be the case, and it boils down to the fact that the firmware “wouldn’t know what to do” when you call System.exit(). Personally I think it should just do the equivalent of popping the top-level view but whatever.

    Note the problem here, when we’re talking about true widgets, on devices where glances are disabled or not supported.

    In this case, the top-level view is like a full-screen glance view — it’s basically a preview of the app as it doesn’t support any user interaction except pressing START (or tapping the screen) to open a 2nd-level view which does support user interaction. This is because UP/DOWN/BACK are already reserved for scrolling through the widget loop and returning to the watchface.

    This means that to the user, the real UI flow looks like this:

    Widget top-level view -> preview page (from user’s POV) (user interaction is limited to “opening the app”, and possibly responding to a long press of the MENU button)

    Widget 2nd-level view -> top-level page of the “real app” (full user interaction is available)

    But if you want the user to be able to directly exit from the 2nd-level view, it’s not really possible (*). (Yeah, I’ve run into this problem too.) There’s always that extra top-level page that they have to dismiss by manually pressing BACK again (or UP/DOWN).

    (* I did try a workaround where the top level view would pop itself based on the previous state of the app, but I abandoned it for reasons I don’t remember haha. So the idea is if the 2nd level view wanted to exit, it would set a flag to signify that an exit was requested, pop itself, and the top level view would pop itself due to the flag.

    One problem is that the user would still see the view popping itself, which wouldn’t look great, even if you choose the least obtrusive view transition animation. If you absolutely need to exit a widget from anywhere besides the top level view, maybe this is worth a shot.)

    EDIT: As jim_m_58 pointed out, the top level view of a widget can't be popped, which is why my workaround didn't work lol. iirc, I did have a different workaround to pop all the way to the top level, which doesn't quite exit the app.

    The only way to cleanly exit a widget is for the user to press BACK at the top level view (and the app can't override the BACK action.)

    I'm still pretty sure System.exit() is not allowed to be called from a widget (for the same reason that a top-level pop is not allowed.)

  • Popping the view seems to do the trick, at least on the devices I tested it with. It is on the top level view where I want to do this, so it looks fine from users perspective. Thanks!

  • You want to try your widget on a real device without glances, as it likely won't work.

  • I was going to try this on a real fenix5+.  It doesn't have glances. and in the sim  with the f5+ I get this when I try to pop the main view.  It works fine with devices that have glances.

    Error: Unhandled Exception
    Exception: Base view is not allowed to pop for widgets
    Stack: 
      - popView() at 704b03c0.mb:4791 0x300057ba 
      - onSelect() at C:\Users\James\workspace-prod\PiTemps\source\PiTempsDelegate.mc:30 0x10001a70 
      - handleEvent() at 704b03c0.mb:1470 0x30004deb 
    
    Encountered app crash.

    I first ran into this back in the 2017 timeframe, and the way one of the garmin folks explained it, was that CIQ didn't know what to go back to when the main view was popped if started from the widget loop.

    At that time, I don't recall the error being this well defined.

    Using System.exit() is just a no-op on devices without glances.  That's what I think happened with popView back in 2017..

  • I was going to try this on a real fenix5+.  It doesn't have glances. and in the sim  with the f5+ I get this when I try to pop the main view.  It works fine with devices that have glances.

    Yes, actually the documentation states that popView() throws an OperationNotAllowedException "if called from background, data field, glance, or watch face app; or of called from the base page of a widget".

    But my code popping the base view is only executed on devices with glance. Basically I have one delegate for all levels, and in onBack() I pop the view. On the non-glance devices the base view is shown as part of the widget carousel and onBack() is blocked there, so it is not executed. On glance devices it correctly takes me back from the base view to the glance or the app menu, depending on where I started the app from.

    But also I catch exceptions and return false, which then executes the default behavior of the back button. So even if because of unforeseen circumstances the popView() is executed on the base page of a widget, there should not be a crash.

    Here is my code:

    public function onBack() as Boolean {
        try {
            // here I have some custom code
            // that I want executed before the view
            // is popped
            WatchUi.popView( WatchUi.SLIDE_RIGHT );
            return true;
        } catch ( ex ) {
            EvccHelper.debugException( ex );
            return false;
        }
    }
    

    I guess I could achieve the same by not calling popView() and just returning false, my custom code would be executed, but then the default behavior of the button kicks in. Maybe that would be the cleaner implementation, at least if the default transition is what you want (I guess SLIDE_RIGHT is the default anyway).

  • Sorry for the bad advice about popping the main view for a widget.

    The real answer (which I had forgotten) is that you can only cleanly exit a widget if the user presses BACK at the top view. (This requires that the BACK action is not overridden in the input/behavior delegate - in onBack() -  which means you can't handle BACK in a special way - e.g. conditionally exit the app.)

    That's why there's no workaround for trying to cleanly exit a widget from a view other than the top level - a clean exit always requires the user to press BACK at the top level, and there's no way to fake it.

    I first ran into this back in the 2017 timeframe, and the way one of the garmin folks explained it, was that CIQ didn't know what to go back to when the main view was popped if started from the widget loop.

    Thanks for reminding me of this.

    Yes we talked about that before. I said that if a widget pops its main view, then CIQ could simply do the same thing that would happen if the user had pressed BACK from the main view (which is to return to the watchface). Your answer was the firmware may not have the ability to do so, due to some implementation detail or another. (e.g. Perhaps it's different parts of the system that would be responsible for handling the normal BACK at top-level behavior vs the behavior that would happen if a widget could pop its top level view.)

    Obviously this behavior isn't going to change, but I'll say I don't think it's logically necessary. It just happens to probably be impossible/impractical to change with the existing design/implementation.

  • (This requires that the BACK action is not overridden in the input/behavior delegate - in onBack() -  which means you can't handle BACK in a special way - e.g. conditionally exit the app.)

    Thanks for the clarification! As far as I understand you could handle BACK in a special way with some limitations. If you return false from your onBack() in the delegate the default behavior would still kick in. So you can add some custom code that is to be executed before the app exits, but you cannot prevent the app to exit. Right?

  • Thanks for the clarification! As far as I understand you could handle BACK in a special way with some limitations. If you return false from your onBack() in the delegate the default behavior would still kick in. So you can add some custom code that is to be executed before the app exits, but you cannot prevent the app to exit. Right?

    Yeah exactly. By "override the back action", I meant returning true from onBack() such that the default behavior doesn't happen. (That's why I didn't want to say "override onBack()").