Invalid Value Error in Object.equals()

Good morning,

I am seeing the following error for my app in the ERA Viewer:

Error Name: Invalid Value
Occurrences: 1
First Occurrence: 2026-01-14
Last Occurrence: 2026-01-14
Devices:
    Edge® 1040 / 1040 Solar: 30.12
App Versions: v1.1.2.1
Languages: fre
Backtrace:
    BasePageMenuInitializationTasks.invoke:89
    AsyncTaskQueue.executeTasks:119

The error occurs in the following if statement. The equals function used is the standard Object.equals() implementation.

if( _pageMenu.equals( WatchUi.getCurrentView()[0] ) ) {
}

Based on your experience, what could trigger an Invalid Value error at this point? Could getCurrentView()[0] being null be the cause?

  • According to the docs equals can get null. What happens if you deliberately pass null? In the simulator? In real device?

    I wish Garmin would include more useful information in these error reports. Like what is the value :) 

  • In the simulator, this appears to work as expected; I can pass null without triggering an exception. Unfortunately, I do not have access to a physical Edge 1040 device to test this behavior on real hardware.

    In addition, I am not aware of the exact circumstances under which the issue occurred. The view stack is driven by user configuration, and the problem was only observed via the ERA Viewer, without any direct contact with the affected user.

  • What are the few lines before and after the if? Maybe the line number is off by a small amount.

  • Yes, I was already about to mention that as well, after I read your post in my other thread.

    Here is the full code in question. In this case, there is a switchToView() call immediately after the if. So, similar to the other thread you commented on, it’s possible the ERA Viewer line attribution is off and the actual cause is the switchToView() call rather than the equals() comparison.

    // Perform the switch if the menu is visible
    public function invoke() as Void {
        // ... If this menu is the current view ...
        if( _pageMenu.equals( WatchUi.getCurrentView()[0] ) ) {
            // Logger.debug( "SwitchViewIfVisibleTask.invoke: switching the view!" );
            
            // ... we do the switch to itself
            WatchUi.switchToView(
                WatchUi.getCurrentView()[0] as View,
                WatchUi.getCurrentView()[1] as InputDelegate,
                WatchUi.SLIDE_IMMEDIATE
            );
        }            
    }

    Maybe the cast to InputDelegate could be the culprit if there is no delegate in that situation, although I would expect this kind of view (a Menu2) not to exist without an InputDelegate. I’ll need to investigate this further.

    Source file:

    github.com/.../BasePageMenuInitializationTasks.mc

  • Maybe the cast to InputDelegate could be the culprit if there is no delegate in that situation, although I would expect this kind of view (a Menu2) not to exist without an InputDelegate

    The cast itself can't directly cause a crash, because Monkey C casts are compile-time only - they have no runtime effect. To be clear, casting in Monkey C doesn't change the actual runtime type of an object (as in Java), but it only changes the assumed compile-time type of the object for the purposes of the type checker (as in TypeScript).

    So what's *not* happening is that a null value (for WatchUi.getCurrentView()[1]) is somehow being coerced at runtime to an InputDelegate, leading to a crash. That is simply impossible.

    Would could happen is that a cast can hide a bug that would have otherwise caused a compile error, leading to a runtime crash. If that's what you meant - meaning that you think the cast is masking the fact that the 2nd arg to switchToView is null and that's causing the crash - well that arg is allowed to be null, so that can't be it either.

    In other words, removing those casts (as View and as InputDelegate) cannot change the runtime behaviour of the code. At most they could prevent the code from compiling, due to type error(s).

    Similarly, adding casts anywhere in your code can't change the runtime behaviour of the code per se, other than to allow code to compile that previously would not compile (due to a type error) and possibly exposing a runtime bug that would not have been possible before (simply because the code didn't compile).

    TL;DR Monkey C casts don't change the meaning/behaviour of the code, they just change the way the code looks to the type checker. However, the fact that Monkey C casts can mask real bugs is why I try to avoid them as much as possible. (They don't even have the same kind of safeguards as TypeScript casts.)

    The easiest way to see this is probably to disable type checking, in which case type casts should have 0 effect at all (as long as they're syntactically correct and refer to valid types, ofc.)

  • TL;DR Monkey C casts don't change the meaning/behaviour of the code, they just change the way the code looks to the type checker.

    Thanks for the explanation! So, type-wise, the second and third parameters passed to WatchUi.switchToView() should be fine.

    The first parameter could theoretically be an issue, since getCurrentView()[0] may be null, while switchToView() does not accept null for the view. However, the preceding if should ensure that this is not the case… hmm. Thinking

  • The first parameter could theoretically be an issue, since getCurrentView()[0] may be null, while switchToView() does not accept null for the view. However, the preceding if should ensure that this is not the case… hmm.

    Yeah I had the exact same thoughts.

    So I have no idea what's happening here tbh

  • This is not the cause of the error, but shouldn't it be casted to "InputDelegate?" (because it can be null)?

    I had some problems with switchToView. Depending on things like whether this is triggered from Menu or Menu2 and other things the current view might not be exactly what you think it is at the exact moment. Though you seem to be checking it in the if, so probably that's also not the case here.

    Ah, wait! Are you switching to the CURRENT view??? Why? It is already the current view. What's the logic behind trying to do it?

    Also what I would do: only call getCurrentView() once in this method, and use the result in a local variable in the rest of the method.

  • Ah, wait! Are you switching to the CURRENT view??? Why? It is already the current view. What's the logic behind trying to do it?

    That’s a workaround for a bug in the SDK. I add and remove items from the Menu2 at runtime, and removing items in particular causes a crash on the next UI update. Switching to the current view is a workaround that avoids the crash.

    Bug report:
    https://forums.garmin.com/developer/connect-iq/i/bug-reports/device-crashes-when-deleting-item-from-active-custommenu

    Thanks for the suggestion to fetch the current view only once, I’ll implement that.