Search code examples
c#xamlmvvmmaui

MAUI MVVM Navigation, how to navigate from page back to MainPage


I'm learning how Maui MVVM works. I want to write code that:

  • starts in MainPage.xaml, where there's a button that takes you to:
  • FirstPage.xaml, where there's a button that takes you to:
  • SecondPage.xaml, where there's a button that takes you back to:
  • MainPage.xaml, where there's still a button that takes you to:
  • FirstPage.xaml and so on

The problem is I can't go from SecondPage.xaml back to MainPage.xaml.

Where's the problem?

The line of code I need to navigate from SecondPage back to MainPage, but it gives an error:

        public AppShell()
        {
            InitializeComponent();
this line>> Routing.RegisterRoute(nameof(View.MainPage), typeof(View.MainPage)); 
            Routing.RegisterRoute(nameof(View.FirstPage), typeof(View.FirstPage));
            Routing.RegisterRoute(nameof(View.SecondPage), typeof(View.SecondPage));
        }

The type or namespace MainPage does not exist in ProjectName.View (Are you missing an assembly reference?)

Alas, I am not missing an assembly reference.
It's worth noting that I moved MainPage.xaml into a View folder, but I still get the message when I move it back to root.

It makes sense that I wouldn't be able to put routing info for the starting page, as there sort-of already is routing code for it in AppShell.xaml:

    <ShellContent
        Title="Main Page"
        ContentTemplate="{DataTemplate local:MainPage}"
        Route="MainPage" />

With all that said, how do I navigate back to MainPage.xaml?

Here's my code for reference

Use the CommunityToolkit.MVVM Nuget package.
Add the dependency injections in MauiProgram.cs:

            builder.Services.AddSingleton<MainPage>();
            builder.Services.AddSingleton<MainPageViewModel>();
            builder.Services.AddSingleton<FirstPage>();
            builder.Services.AddSingleton<FirstPageViewModel>();
            builder.Services.AddSingleton<SecondPage>();
            builder.Services.AddSingleton<SecondPageViewModel>();

Add the routing information in App.Shell.xaml.cs:

        public AppShell()
        {
            InitializeComponent();
            Routing.RegisterRoute(nameof(View.FirstPage), typeof(View.FirstPage));
            Routing.RegisterRoute(nameof(View.SecondPage), typeof(View.SecondPage));
        }

For each page, do the following:
Create a ViewModel class for each Page.
Add Binding Context to the ViewModels from the Page's code behind:

public partial class FirstPage : ContentPage
{
    public FirstPage(FirstPageViewModel firstPageViewModel)
    {
        InitializeComponent();
        BindingContext = firstPageViewModel;
    }
}

In Page.xaml, make sure that the file can both access and talk to its corresponding ViewModel, and add a button (or something) that has a command (note this code is generic for every Page):

<ContentPage ...
             xmlns:viewmodel="clr-namespace:ProjectName.ViewModel"
             x:DataType="viewmodel:PageViewModel">

     <Button ... 
             Command="{Binding NavigateToPageCommand}"/>
</ContentPage>

Finally, make sure that there's a corresponding method for the Command in PageViewModel.cs, like so:

    public partial class PageViewModel 
    {
        [RelayCommand]
        async Task NavigateToPage()
        {
            await Shell.Current.GoToAsync(nameof(View.Page));
        }
    }

EDIT: in response to gratefully-accepted advice

(1) Name your home page HomePage. Don't confuse yourself.
OK.

(2) In your MainPage.xaml.cs. The first line is something like namespace ProjectName.View; Take away the View.
I don't have it like that. Here's my file MainPage.xaml.cs:

namespace EMS_MOBILE_MAUI
{
    public partial class MainPage : ContentPage
    {
        public MainPage(MainPageViewModel mainPageViewModel)
        {
            InitializeComponent();
            BindingContext = mainPageViewModel;
        }
    }
}

(3) Change the DI for MainPage from AddSingleton to AddTransient.
OK.
Can you tell me if this next statement is correct? It's my interpretation of the difference between AddSingleton and AddTransient:

AddSingleton means exactly that: a static, one-time page. On one hand, you can change the state of the Page, navigate away from it, come back, and it'll still be in that state.
On the other hand, AddTransient will cause the Page to get re-"newed" if you navigate to another page and back again.

(3) You shouldn't have both of these lines of code:
MauiProgram.cs:

Routing.RegisterRoute(nameof(View.MainPage), typeof(View.MainPage));

AppShell.xaml.xs:

    <ShellContent
        Title="LoginPage"
        ContentTemplate="{DataTemplate local:MainPage}"
        Route="MainPage" />

I don't have both of these lines. I only have the second line.
Since the routing for MainPage is already in AppShell.xaml.cs, I don't use the other routing line. I put that line in at the beginning of my question as demonstration of where I thought the problem might lie.

(4) Read the docs. Carefully. For example, I bet you didn't know that you can navigate back with "..".
Yeah, you're right. I'm not thinking about navigation in terms of a stack that you push/pop. I will do that.

In conclusion, the only actual change I can make to my code is changing the MainPage's DI from AddSingleton to AddTransient, which doesn't change the behavior in that I still can't navigate from any Page that I have back to the MainPage. I bet if I read those docs instead of skimming I'll find my answer.


Solution

  • Okay.

    The type or namespace MainPage does not exist in ProjectName.View (Are you missing an assembly reference?)

    First, I advise you to name your page HomePage. MainPage is something very specific, I see that you are new, you will only confuse yourself.

    Second, in your MainPage.xaml.cs. The first line is something like:

    namespece ProjectName.View;
    

    You do not have to move around the file. This line is what changes when you move it around, this is why you get it working sometimes, and sometimes not.

    And if you have in this line .View you go with View.MainPage. And if you do not, you go with just MainPage.

    Second:

    builder.Services.AddSingleton<MainPage>();
    

    This should be changed to transient.

    builder.Services.AddTransient<MainPage>();
    

    Shell requirement. (Bad things happen otherwise).

    Third:

    Do not put routes like that:

    Routing.RegisterRoute(nameof(View.MainPage), typeof(View.MainPage)); 
    

    At the same time with routes in the XAML:

    Route="MainPage"
    

    About navigation: Read this here, start to end: https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/shell/navigation

    Some clarification.

    Let's say MainPage is my home, and I have SecondPage defined as global route, that I want to call from anywhere.

    This means MainPage stays in the XAML.

    <ShellContent
            Title="Main Page"
            ContentTemplate="{DataTemplate local:MainPage}"
            Route="MainPage" />
    

    And SecondPage stays in the CS:

    Routing.RegisterRoute(nameof(View.SecondPage), typeof(View.SecondPage));
    

    When the app starts, it will look for the topmost ShellItem in the XAML (that is your main page), and navigate you there.

    The navigation stack looks like this //MainPage.

    Then you can call GoToAsync("SecondPage"). You will be navigated to SecondPage. The navigation stack looks like this //MainPage/SecondPage.

    To go back you can navigate with ".." (the default behavior of the back button will do this).

    Common mistake here is to call "//SecondPage". To be able to leave SecondPage as the only page of the stack, it has to be registered as ShellItem. (You need to add it the way you add your MainPage).

    Start with two pages, do not try to write everything at once, because it will be nearly impossible to fix all mistakes at once. Build your project step by step.