MVUX: ListState AddAsync Triggers Null Callback - Why?

Alex Johnson
-
MVUX: ListState AddAsync Triggers Null Callback - Why?

Investigating a peculiar behavior in MVUX with ListState<Uri>.AddAsync. It appears that the Selection/State.Empty.ForEach callback is triggered with a null value before the expected selected value. This article dives into the issue, explores the reproduction steps, and discusses the expected behavior.

Current Behavior

The issue arises when calling .AddAsync(uri,ct) on a ListState<Uri>.Async( ... .Selection(AnyUriState), where Uri currently holds the value of https://platform.uno/studio/. Ideally, the AnyUriState.Empty(this).ForEach( operator callback handler should only be invoked with the added and selected value. However, it's being called with null first, followed by the correct value of https://platform.uno/studio/. This suggests a potential reset caused by .Empty(this), which shouldn't be the case.

This behavior can lead to unexpected side effects, especially if the callback relies on a valid Uri value. It introduces a race condition where the callback might execute with a null value before the intended Uri is available.

Expected Behavior

The desired behavior is straightforward: the ForEach operator callback handler should only be called with the value that has been added and selected. No null values should be passed during the selection process. This ensures that the callback always operates on a valid Uri and avoids potential errors or inconsistencies in the application's state.

How to Reproduce It

To reproduce this issue, follow these steps using the provided sample project:

  1. Start the app. This will display the MainPage.

  2. If not preserved, navigate to MainModel and set breakpoints on:

    • HistorySelectionChanged Callback Handler (Line 1 - logger)
    • CurrentUrlChanged in the AddAsync and TrySelectAsync lines, located towards the bottom of the method.
  3. In the WebView2, which initially displays the Uno Homepage, click on "Platform" in the header navigation. You might need to use the Hamburger Icon first, as the WebView2 might not automatically resize on window resizing, to reveal the main website navigation to work properly.

  4. Observe the console output. A correct output should resemble:

    info: UnoWebViewReproApp1.Presentation.MainModel[0]
          CurrentUrlChanged Adding https://platform.uno/platform/ to NavigationHistory
    info: UnoWebViewReproApp1.Presentation.MainModel[0]
          CurrentUrlChanged Selected https://platform.uno/platform/ in NavigationHistory
    warn: UnoWebViewReproApp1.Presentation.MainModel[0]
          HistorySelectionChanged Got parameter: https://platform.uno/platform/, this is type of: Uri
    info: UnoWebViewReproApp1.Presentation.MainModel[0]
          HistorySelectionChanged Selected Uri from CurrentUrl: https://platform.uno/platform/
    

    This output indicates proper behavior.

  5. Now, click on "Studio" on the Homepage to navigate to that page.

  6. Inspect the console output again. This time, you'll observe the problematic behavior:

    info: UnoWebViewReproApp1.Presentation.MainModel[0]
          CurrentUrlChanged Adding https://platform.uno/studio/ to NavigationHistory
    info: UnoWebViewReproApp1.Presentation.MainModel[0]
          CurrentUrlChanged Selected https://platform.uno/studio/ in NavigationHistory
    warn: UnoWebViewReproApp1.Presentation.MainModel[0]
          HistorySelectionChanged Got parameter: (null), this is type of: (null)
    warn: UnoWebViewReproApp1.Presentation.MainModel[0]
          HistorySelectionChanged Got parameter: https://platform.uno/studio/, this is type of: Uri
    info: UnoWebViewReproApp1.Presentation.MainModel[0]
          HistorySelectionChanged Selected Uri from CurrentUrl: https://platform.uno/studio/
    

    Notice the null value being logged before the actual Uri.

By setting breakpoints as described, you can trace the execution flow and observe the null value being passed to the HistorySelectionChanged callback, even though it's defined to accept a non-nullable Uri type. Further debugging reveals that the null value leads to the if statement not being entered and thus the method exiting without proper execution. Subsequent calls, triggered by TrySelectAsync and UpdateAsync, then proceed with the correct Uri value.

Detailed Breakdown of the Issue

When AddAsync is called, the value is a valid URL (not null). This confirms that the Uri being added is indeed a valid URL.

The HistorySelectionChanged callback is triggered with a null argument, leading to its initial log entry.

Debugging steps lead to CurrentUrlChanged > TrySelectAsync.

Further debugging steps reach HistorySelectionChanged > if statement. Due to the null value, the condition is not met, and the statement is skipped, causing the method to exit.

Subsequently, the execution seems to react to TrySelectAsync, leading to a second call to HistorySelectionChanged. This time, the call includes the actual URL, resulting in the expected behavior and logging.

UpdateAsync is then called, and the code proceeds as expected with the correct values, instead of null.

Environment

  • IDE: VS 2026 Windows
  • Affected Platform(s): Desktop, Build tasks
  • Visual Studio: 2026 (version: 18.0.0)

Additional Information

The introduction of this null value was previously mentioned in #2927. In that scenario, a connected TextBox bound to the Selected Item fails to display any value and remains stuck on the null value, failing to update with the subsequent correct value.

This is especially concerning because it indicates that the initial null value can have lasting effects on the application's state, even if it's followed by a valid value. This behavior needs to be addressed to ensure the reliability and consistency of MVUX applications.

Conclusion

The unexpected triggering of the Selection/State.Empty.ForEach callback with a null value before the actual selected value in MVUX's ListState<Uri>.AddAsync presents a significant issue. The reproduction steps clearly demonstrate this behavior, and the analysis reveals a potential race condition and inconsistencies in the application's state. Addressing this issue is crucial for ensuring the stability and predictability of MVUX applications.

Further Reading: For more in-depth information about Uno Platform's architecture and MVUX, check out the official Uno Platform Documentation. This resource provides detailed explanations of the framework's components and best practices for building robust applications. Understanding the underlying architecture can help you better troubleshoot and resolve issues like the one described in this article.

You may also like