Menu view popping (in sim)

My app has the app view -> menu view 1 -> menu view 2

For the Edge 1000 (a CIQ 2.4 device), in the simulator clicking the back button in menu view 2 pops all the way back to the app view.

For the Edge 1030 (a CIQ 3 device), in the simulator clicking the back button in menu view 2 pops back to menu view 1 (as expected).

Is the 2.4 issue a general one or is it just in the simulator?

  • In the delegate you use onSelect with Menu2 and onMenuItem with Menu. Menu2 is the the new version of Menu that was introduced in 3.0.0, not the second menu..

    Yes, I realize that.

    I'll call the second menu B.

    main view -> menu A -> menu B

  • Here's my attempt at recreating the problem, all contained in a single source file.

    This is a device app which I built and tested in the simulator for Edge 1000 and Edge 1030, based on the generic device app template created by the CIQ wizard.

    App Explanation
    1) Initial view doesn't display or do anything, except wait for the user to tap the screen (onSelect()). (The line which would normally display the default layout with the monkey and "click the menu button" is commented out, because I don't want any resource dependencies)
    2) Tap the screen to push Menu A, which is a WatchUi.Menu
    3) Tap the menu item labelled "Random Item A1" to push Menu B, which is also a WatchUi.Menu
    4) Press Back. In my tests, the app returns to Menu A
    5) Press Back. In my tests, the app returns to the initial view

    There are no explicits pops in the code, nor are there any attempts to handle/override onBack.

    I hope I haven't missed anything here.

    Maybe if you could post a modified/cut-down sample of your code so we can run it and see the same behaviour? And/or possibly see what might be different about your code compared to ours?

    Here's the code on pastebin with syntax highlighting: https://pastebin.com/0PGRdevb

    using Toybox.Application;
    using Toybox.WatchUi;

    class TestMenuAppApp extends Application.AppBase {

    function initialize() {
    AppBase.initialize();
    }

    // onStart() is called on application start up
    function onStart(state) {
    }

    // onStop() is called when your application is exiting
    function onStop(state) {
    }

    // Return the initial view of your application here
    function getInitialView() {
    return [ new TestMenuAppView(), new TestMenuAppDelegate() ];
    }

    }


    class TestMenuAppView extends WatchUi.View {

    function initialize() {
    View.initialize();
    }

    // Load your resources here
    function onLayout(dc) {
    //setLayout(Rez.Layouts.MainLayout(dc));
    }

    // Called when this View is brought to the foreground. Restore
    // the state of this View and prepare it to be shown. This includes
    // loading resources into memory.
    function onShow() {
    }

    // Update the view
    function onUpdate(dc) {
    // Call the parent onUpdate function to redraw the layout
    View.onUpdate(dc);
    }

    // Called when this View is removed from the screen. Save the
    // state of this View here. This includes freeing resources from
    // memory.
    function onHide() {
    }

    }

    class TestMenuAppDelegate extends WatchUi.BehaviorDelegate {

    function initialize() {
    BehaviorDelegate.initialize();
    }

    function onSelect() {
    var menuA = new WatchUi.Menu();
    menuA.setTitle("Menu A");
    menuA.addItem("Random Item A1", null);
    WatchUi.pushView(menuA, new MenuADelegate(), WatchUi.SLIDE_UP);
    return true;
    }

    }

    class MenuADelegate extends WatchUi.MenuInputDelegate {

    function initialize() {
    MenuInputDelegate.initialize();
    }

    function onMenuItem(item) {
    var menuB = new WatchUi.Menu();
    menuB.setTitle("Menu B");
    menuB.addItem("Random Item B1", null);
    WatchUi.pushView(menuB, new MenuBDelegate(), WatchUi.SLIDE_UP);
    return true;
    }
    }

    class MenuBDelegate extends WatchUi.BehaviorDelegate {

    function initialize() {
    BehaviorDelegate.initialize();
    }

    function onMenuItem(item) {
    // do nothing
    }
    }
  • That's pretty much what I did in the project I attached to post 2. and yours is also working correctly.

    Sounds like he's using onSelect in the second delegate, but that doesn't skip the 1st menu for me in the sim, so it's something else. I've tried it as both a widget and device-app, as I'm not sure what app type he's using.
  • That was useful.

    The second menu is created from the responseCallback. function passed to makeWebRequest.

    It seems that creating the menu there confuses CIQ < 3.

    I'm not sure if there's a way around it.


  • I'm thinking when you do the makeWebRequest() menuA should be popped (not by you, but but the system, as that's what happens when you select something with Menu), so it may be the 1030 that's incorrect..

    If you don't push MenuB in onReceive, what's on the screen? I'd expect the main view.

    I'm wonder how this works on a real device. Do you have a 1000 or a 1030? In the sim, a makeWebRequest is fast, but can take much longer on a real device, so I'd probably look at doing this is a different way.
  • @dpawlyk I assume you want the second menu to be inaccessible until the web request is finished. In that case, you probably want to block the entire UI while the request is active.

    Try this:
    - Push the second menu after makeWebRequest (in the same execution thread/context), not in the callback. This should avoid your problem with the first menu automatically popping itself or whatever
    - Use a state variable to determine whether makeWebRequest has finished. i.e. Set to false before request, set to true in callback
    - When the second menu is initially displayed, if makeWebRequest is still active, push a progressbar immediately. This can be a "busy-wait" progress bar with text like "Loading" or whatever. Set a 2nd state variable to signify that the progressbar was pushed
    - In the makeWebRequest callback, if the progressbar was pushed, call WatchUi.popView()
  • - Push the second menu after makeWebRequest (in the same execution thread/context), not in the callback. This should avoid your problem with the first menu automatically popping itself or whatever

    The items in the second menu are created from the results the callback gets.

  • I'm thinking when you do the makeWebRequest() menuA should be popped (not by you, but but the system, as that's what happens when you select something with Menu), so it may be the 1030 that's incorrect..

    So, the difference is between Menu and Menu2. Menu2 doesn't pop on select because you can interact with the items (change checkbox state).

    I'm wonder how this works on a real device. Do you have a 1000 or a 1030? In the sim, a makeWebRequest is fast, but can take much longer on a real device, so I'd probably look at doing this is a different way.

    I have a 1030. It works fine on the device.

  • The items in the second menu are created from the results the callback gets.



    Okay, push a dummy view which:
    1) Displays nothing
    2) Immediately pushes a progressbar for its first onShow(), if necessary (if the webrequest is still in progress)
    (If the progressbar was pushed, when the webrequest completes, call WatchUi.popView())
    3) For its second onShow() (or if the progressbar was not pushed), pushes the menu, created from the callback results

    I don't think this is an impossible problem to solve. The code will just be a bit ugly....

    EDIT: I don't even think you need the dummy view. You could probably just drive all of this from the first menu, with a state variable (which the dummy view would need anyway), since you need to push a progressbar anyway.

    Obviously you will have to store the callback results somewhere else that's accessible that everyone, in some shared "config/state" object that's passed around the views. If you're in a hurry you could just use global vars, too. (No shame in that, especially when you need to save memory, as long as you don't plan on easily reusing the code in a different project....)

    For myself, I've also found that certain combinations of pushes/pops just don't work. For example, IIRC you can't push two consecutive views in the same thread/context/event handler; it just won't work the way you want it to.

    ---
    Simplified solution
    • In the first menu, add state variable waitingForWebRequest (init to false)
    • When the web request is triggered (by selecting something in first menu, right?), set waitingForWebRequest to true. Push progressbar
    • In the webrequest callback, save results somewhere first menu can access it. Pop view
    • In the first menu onShow(), if waitingForWebRequest == true, then:
      • Create 2nd menu from results
      • Push 2nd menu

    (Of course another way to do this would just be to store a reference to the progress bar and use that instead of the state variable.)

    What sucks about this solution is you may see the first menu redrawn, briefly after the progress bar disappears.

    The more complex solution could give you a blank screen briefly, after the progress bar disappears.
  • So, the difference is between Menu and Menu2. Menu2 doesn't pop on select because you can interact with the items (change checkbox state).


    Correct. They function differently as far as what happens when an item is selected. There's a Menu2Sample in the SDK, and compare how things work vs the project I posted which uses Menu.