reactiveui / ReactiveUI

An advanced, composable, functional reactive model-view-viewmodel framework for all .NET platforms that is inspired by functional reactive programming. ReactiveUI allows you to abstract mutable state away from your user interfaces, express the idea around a feature in one readable place and improve the testability of your application.

Home Page:https://www.reactiveui.net

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Bug]: On Maui initial NavigateAndReset pushes the same screen twice

kyurkchyan opened this issue ยท comments

Describe the bug ๐Ÿž

I have been following the official ReactiveUI.Samples Cinephile Maui sample project to create my own Maui Reactive UI project. I've set up basic MainPage and MainViewModel. When I launch the app the MainPage is pushed twice to the navigation stack.

The bug is present in the official sample project as well.

When I looked into the source code of the RoutedViewHost here's what I've noticed

  1. When the RoutedViewHost is created SyncNavigationStacksAsync
  2. SyncNavigationStacksAsync notices that the navigation stacks are different - RoutingState has 1 view model and the Navigation stack on the Maui side has none, and pushes the main view model
  3. Then Router.Navigate is invoked, which pushes the same page again

I guess that SyncNavigationStacksAsync is invoked to ensure that the screens pushed into the RoutingState while the navigation page wasn't displayed are pushed to the stack. However, I am unsure why the Navigate observable is fired again during the initialization.

I've copied RoutedViewHost to my project and simply added Skip(1) here

....
 Router?
               .Navigate
               .Skip(1)
               .ObserveOn(RxApp.MainThreadScheduler)
               .SelectMany(_ => PagesForViewModel(Router.GetCurrentViewModel()))
               .SelectMany(async page =>
               .....

This solved the problem for me. However, I am not sure whether this is the right solution in the global context.

Step to reproduce

  1. Create an empty Maui project
  2. Add ReactiveUI.Maui nuget
  3. Create a MainViewModel that derives from ReactiveObject and implements IRoutableViewModel
  4. Create MainPage that derives from ReactiveContentPage<MainViewModel>
  5. Register the screen, router and the MainPage <-> Main view model mapping
    public static class AppBootstrapper
    {
        public static MauiAppBuilder UseAppBootstrapper(this MauiAppBuilder builder)
        {
            var router = new RoutingState();
            var screen = new AppBootstrapScreen(router);
            Locator.CurrentMutable.RegisterConstant(screen, typeof(IScreen));
            Locator.CurrentMutable.Register(() => new MainPage(), typeof(IViewFor<MainViewModel>));
            router
                .NavigateAndReset
                .Execute(new MainViewModel(screen))
                .Subscribe();

            return builder;
        }
        
        public static Page CreateMainPage()
        {
            return new RoutedViewHost();
        }
        
        private class AppBootstrapScreen : ReactiveObject, IScreen
        {
            public AppBootstrapScreen(RoutingState router)
            {
                Router = router;
            }
            
            public RoutingState Router { get; protected set; }
        }
    }
  1. call UseAppBootstrapper inside the MauiProgram
  2. Set the main page inside the App.xaml.cs
  3. Launch the app
  4. Observe that the main page is pushed twice

Reproduction repository

https://github.com/kyurkchyan/ReactiveUI.Maui.NavigationBug

Expected behavior

The main screen should be pushed only once when the app is initialized

Screenshots ๐Ÿ–ผ๏ธ

navigation_bug

IDE

No response

Operating system

Mac

Version

14.0 (23A344)

Device

iPhone Simulator

ReactiveUI Version

19.5.1

Additional information โ„น๏ธ

No response

Thanks for the decent bug report. Will have a look this weekend I hope.

Has anyone found the right solution for this problem or should we just use Skip as suggested?

Skip is your best bet at the moment. Sorry haven't got to checking into this one yet.

Happy for a community submitted PR for anything like this one.

Ok, thanks! I'm not sure I could help, since I don't have experience in contributing, but I'll give it a try.

In terms of contributing, happy to help you out with any of the steps.

Here's some tips I would recommend

Get RxUI source code using a git client. Make a fork so you can contribute back. GitHub web UI makes this easy.
Include the rxui csproj's instead of a NuGet reference in your solution to test with
Create a branch
push the code fix to the branch
ideally add a unit test if able with your fix. We don't have MAUI runners at the moment for unit testing so this step may not be possible.
Create a pull request.

Happy to help out with any other information you need.

@glennawatson I think that filtering Navigate to execute only when the NavigationStacks have different sizes solves the problem and makes the call to SyncNavigationStacksAsync unecessary (I left it on my PR, just in case).

I understand this is resolved, I will close it now, if there is still an issue please raise an issue with the new findings. Thank you

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.