Under Review
over 2 years ago

App crashes when menu items are dynamically updated.

Hello,

I'm experiencing an issue that seems to be a Connect IQ bug. After replacing all menu items, the app crashes when I try to do a selection. The crash happens only on a real device (MK2), on the simulator everything works without any issues.

Here is a video demonstration of the crash: https://www.youtube.com/shorts/FLbmo3iQcMw

Source code for the repro project: https://drive.google.com/file/d/18oVORw8lScu4Jmdz8TU0gjnHsopOsXXS/view?usp=sharing

All the custom code is located in the SettingsMenu.mc

import Toybox.Graphics;
import Toybox.Lang;
import Toybox.WatchUi;
import Toybox.Application;

function pushSettingsMenu() as Void {
    var settingsMenu = new $.SettingsMenu(80, Graphics.COLOR_WHITE);
    settingsMenu.setMenuItems();
    WatchUi.pushView(settingsMenu, new $.SettingsMenuDelegate(settingsMenu), WatchUi.SLIDE_UP);
}

//! This is the sub-menu in the Wrap custom menu
class SettingsMenu extends WatchUi.CustomMenu {
    //! Constructor
    //! @param itemHeight The pixel height of menu items rendered by this menu
    //! @param backgroundColor The color for the menu background
    public function initialize(itemHeight as Number, backgroundColor as ColorType) {
        CustomMenu.initialize(itemHeight, backgroundColor, {});
        System.println("Settings menu initialized");
    }

    //! Draw the menu title
    //! @param dc Device Context
    public function drawTitle(dc as Dc) as Void {

        System.println("Settings menu drawTitle");

        var centerX = dc.getWidth() / 2;
        dc.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_BLACK);
        dc.clear();
        dc.setColor(Graphics.COLOR_DK_GRAY, Graphics.COLOR_DK_GRAY);
        dc.setPenWidth(3);
        dc.drawLine(0, dc.getHeight() - 2, dc.getWidth(), dc.getHeight() - 2);
        dc.setPenWidth(1);
        dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_TRANSPARENT);
        dc.drawText(dc.getWidth() / 2, dc.getHeight() / 3 * 2, Graphics.FONT_MEDIUM, "Back to Tables", Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER);
        dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_WHITE);
        dc.fillPolygon([[centerX, 10] as Array<Number>,
                        [centerX + 5, 15] as Array<Number>,
                        [centerX - 5, 15] as Array<Number>] as Array< Array<Number> >);
    }

    var tmp as Boolean = false;

    public function setMenuItems() as Void {

        System.println("Settings menu setMenuItems");

        // Remove all items
        var itemDeleted = false;

        do {
            itemDeleted = deleteItem(0);
        } while (itemDeleted);

        // Add Items
        var authenticated = tmp;// $.getApp().isAuthenticated();

        if (!authenticated) {
            addItem(new $.CustomWrapItem(:connectItemId, "Pair", Graphics.COLOR_BLACK));
            tmp = true;
        }
        else {
            addItem(new $.CustomWrapItem(:reloadItemId, "Reload", Graphics.COLOR_BLACK));
            addItem(new $.CustomWrapItem(:unpairItemId, "Unpair", Graphics.COLOR_BLACK));
            tmp = false;
        }
    }
}

//! This is the menu input delegate for the Wrap bottom menu
class SettingsMenuDelegate extends WatchUi.Menu2InputDelegate {

    private var _settingsMenu as SettingsMenu;

    //! Constructor
    public function initialize(settingsMenu as SettingsMenu) {
        Menu2InputDelegate.initialize();
        _settingsMenu = settingsMenu;
    }

    //! Handle an item being selected
    //! @param item The selected menu item
    public function onSelect(item as MenuItem) as Void {
    
        System.println("onSelect called");

        if (item.getId() == :connectItemId) {
            _settingsMenu.setMenuItems();
            System.println("connect");
        }


        if (item.getId() == :unpairItemId) {
            System.println("unpair");
            //Application.Storage.deleteValue("refresh_token");
            //Application.Storage.deleteValue("access_token");
            //Application.Storage.deleteValue("tables");

            _settingsMenu.setMenuItems();
        }

        WatchUi.requestUpdate();
    }
}

class CustomWrapItem extends WatchUi.CustomMenuItem {

    private var _label as String;
    private var _textColor as ColorValue;

    //! Constructor
    //! @param id The identifier for this item
    //! @param label Text to display
    //! @param textColor Color of the text
    public function initialize(id as Symbol, label as String, textColor as ColorValue) {
        CustomMenuItem.initialize(id, {});
        _label = label;
        _textColor = textColor;
    }

    //! Draw the item string at the center of the item.
    //! @param dc Device Context
    public function draw(dc as Dc) as Void {
        var font = Graphics.FONT_SMALL;
        if (isFocused()) {
            font = Graphics.FONT_LARGE;
        }

        if (isSelected()) {
            dc.setColor(Graphics.COLOR_BLUE, Graphics.COLOR_BLUE);
            dc.clear();
        }

        dc.setColor(_textColor, Graphics.COLOR_TRANSPARENT);
        dc.drawText(dc.getWidth() / 2, dc.getHeight() / 2, font, _label, Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER);
    }
}

CIQ_LOG.YML has the following record:

---
Error: Array Out Of Bounds Error
Time: 2022-10-17T13:42:16Z
Part-Number: 006-B3258-00
Firmware-Version: '16.00'
Language-Code: eng
ConnectIQ-Version: 4.1.5
FilenameAppName
AppnameAppName
Stack:

I hope you can help me to understand if it's really a Connect IQ Bug or not and if there are any ways to resolve it.