CommunityToolkit / MVVM-Samples

Sample repo for MVVM package
Other
1.12k stars 220 forks source link

Navigation sample #21

Open hansmbakker opened 4 years ago

hansmbakker commented 4 years ago

It would be great to have a sample focusing on navigation between pages, showing how to pass data between them, how to create the ViewModels using IoC and wiring up the ViewModels with the page views the user is navigating to.

Sergio0694 commented 4 years ago

That is a great suggestion, we did plan to also add some docs to show the process of creating and using a navigation service, at least as an example of how to go about writing platform specific services. This one in particular is used pretty often, so it would make sense to have a sample for it I think (at least with the basic concepts).

cc. @michael-hawker should we add an actual working sample in the app or just a docs page for this?

michael-hawker commented 4 years ago

@Sergio0694 I think both would be good since it's a common ask. 🙂

ThomasDallmeier commented 3 years ago

Hi anything new with a navigation service ?

jader1313 commented 3 years ago

As long as the "official" example does not come out, could anyone indicate an example using WTC.MVVM? I am suffering a lot trying to implement this.

Sergio0694 commented 3 years ago

I'll put together a minimal sample and post it here as a reference, at least for now. Will update in a bit 😄

EDIT: alright, so here's a few examples. One of the things I mentioned before was that there's really a number of ways to go about doing this, and people might want to tweak the implementation depending on their specific needs. For example, some developers might not be interested in having a platform-agnostic backend, so they might want to add framework-specific types directly on the navigation service (for instance, by passing the page type directly). Others might want to abstract this instead, so in that case you might want to have a service implementation that internally stores a dictionary of sorts to map viewmodel types to page types, and then perform the navigation directly. Some might want a more "pattern oriented" approach, for instance using some kind of standard naming practice for viewmodels, etc.

Here's the simples possible approach for this, with a framework-specific solution (wouldn't recommend this):

// Navigation interface
public interface INavigationService
{
    bool CanGoBack { get; }
    void GoBack();
    void Navigate<T>(object args = null);
}

// UWP navigation service
public class NavigationService : INavigationService
{
    private readonly Frame frame;

    public NavigationService(Frame frame)
    {
        this.frame = frame;
    }

    public bool CanGoBack => this.frame.CanGoBack;

    public void GoBack() => this.frame.GoBack();

    public void Navigate<T>(object args)
    {
        this.frame.Navigate(typeof(T), args);
    }
}

And then you'd use it something like this, for instance (eg. in a single-frame application):

// ...This is from App.xaml.cs, as usual
rootFrame = new Frame();

// Configure your services somewhere after that point, and add the navigation service too
Ioc.Default.ConfigureServices(
    new ServiceCollection()
    .AddSingleton<INavigationService>(new NavigationService(rootFrame))
    .BuildServiceProvider());

// ...Then whenever you need to navigate, you can do:
Ioc.Default.GetRequiredService<INavigationService>().Navigate<SomePage>();

// ...Or, using dependency injection in your viewmodels
this.navigationService.Navigate<SomePage>();

A better approach could be, for instance, to have the navigation service store a mapping of viewmodel types:

public class NavigationService : INavigationService
{
    private readonly Dictionary<Type, Type> viewMapping = new()
    {
        [typeof(MainPageViewModel)] = typeof(MainPage),
        // Other viewmodel types...
    };

    private readonly Frame frame;

    public NavigationService(Frame frame)
    {
        this.frame = frame;
    }

    public bool CanGoBack => this.frame.CanGoBack;

    public void GoBack() => this.frame.GoBack();

    public void Navigate<T>(object args)
    {
        this.frame.Navigate(this.viewMapping[typeof(T)], args);
    }
}

So that then you could use the navigation service from a framework-agnostic viewmodel, by just doing:

this.navigationService.Navigate<SomeViewModel>();

And the platform specific service would look up the target page type automatically.

This is just the general idea, as I've mentioned there could be plenty of other customizations for this, like:

We could definitely add a sample docs page for this in the future though 😄

Gerardo-Sista commented 3 years ago

Yes, a small sample app showing navigation should be really appreciated and helpful!

jasonxz commented 3 years ago

A sample including url-based navigation would be really nice.

Javier118 commented 3 years ago

I wanted to ask how to do Navigate(typeof(Page2));

oscarjaergren commented 2 years ago

Yeah a sample would be good

meenanarendra commented 2 years ago

Please provide a WPF sample application with Navigation.

pradeepkumar-devaraj commented 2 years ago

I currently work on migrating our WPF application from MVVM Light to MVVM Toolkit. I am stuck at implementing the navigation functionality. It would be great if a WPF sample application is provided. Thanks.

michael-hawker commented 2 years ago

@Sergio0694 I know you've been sprucing up the sample app, is this on your list of things to add there?

lloydjatkinson commented 2 years ago

It would be good if MVVM Toolkit had a navigation mechanism already built in...

hassan-gasemi commented 2 years ago

@Sergio0694 Hi. Is there a best practice for setting rootFrame? In your snippet, rootFrame is set in App.xaml but in Wpf our frame usually lay in MainWindow.

curia-damiano commented 1 year ago

+1 interested in having this feature in the dotnet Community Toolkit, thanks!

snow-jallen commented 1 year ago

Any update on navigation in CommunityToolkit?

Sergio0694 commented 1 year ago

There are no plans to add built-in APIs related to navigation at this time. We'd be willing to accept a community contribution adding a navigation sample, provided it meets all requirements for code quality, documentation, etc. 🙂

lloydjatkinson commented 1 year ago

What is the reasoning behind not providing this?

michael-hawker commented 1 year ago

@lloydjatkinson we don't want to add specific APIs to the base library as Navigation tends to be platform specific for its implementation, and the library is platform-agnostic.

However, we welcome a sample that demonstrates how to do this pattern within the framework of our sample app. We just haven't had time to invest in this space above, though @Sergio0694 provided an example above from one of his own projects.

If someone wants to contribute a PR for this scenario based on that or some other implementation to the sample app repo, please feel free, as Sergio calls out above as well.

mrlacey commented 1 year ago

In addition to navigation often being platform specific, there is no one right or even best way to do this. For example, there are solid arguments for and against navigation being between parts of the UI or between ViewModels. Similarly, there are arguments for and against coupling views and view models on a one-to-one basis. There is no one right way to do this, and the approach taken is often driven as much by personal preference as the application's needs. If only a single navigation example were provided, it might easily be interpreted as being the way that must or "should" be used. Without a solid explanation of where, when, and why different approaches may be more or less appropriate, any single sample technique could encourage people to use something that is a poor choice for their application.

mstasak commented 1 year ago

One example can be found if you create a Template Studio project (Windows App SDK) and allow it to generate a navigation service. It is based on ViewModels, not Views/Pages. Sadly, at present it doesn't really show you how to cancel a navigation or receive an event before the navigation occurs (so as to explicitly update bindings and/or commit changes to a database, or prompt the user to save/discard/cancel). Anyway, it is a study-able example, and not too complex.

abdes commented 9 months ago

In addition to navigation often being platform specific, there is no one right or even best way to do this. For example, there are solid arguments for and against navigation being between parts of the UI or between ViewModels. Similarly, there are arguments for and against coupling views and view models on a one-to-one basis. There is no one right way to do this, and the approach taken is often driven as much by personal preference as the application's needs. If only a single navigation example were provided, it might easily be interpreted as being the way that must or "should" be used. Without a solid explanation of where, when, and why different approaches may be more or less appropriate, any single sample technique could encourage people to use something that is a poor choice for their application.

The most confusing issue is that if you are not a seasoned MVVM expert, it's quite difficult to make those choices. On one hand you get the forced WinUI NavigationView way of Frame/Page navigation. On the other hand, if you are writing an application with a menu bar, MDI and cross frame navigation, you are faced with the dilemma of either using application wide messaging (how does this work well/not with MVVM ViewModel first approach?) or with child->parent links which break the testability.

I believe that guidance towards a set of common scenarios, including the cases above (i.e. menu bar, cross frame nav), and how they best fit the philosophy of CommunityToolkit.MVVM would be welcome. Yes, it's not the bible of navigation in MVVM, Yes it will not be exhaustive, but Yes it will greatly help with real world apps that go beyond just a simple NavigationView.

mstasak commented 9 months ago

Yes, even if there's no one superior approach for all situations, it would help to have some starting point, and perhaps a discussion of the alternative approaches and their benefits and pitfalls. SPA with a redirectable content frame is a fairly trivial approach, but you still need to decide between a forward-back stack navigation or a freely addressable network of content targets. You still need to decide if a content target will be abandoned/lost or persisted or reconstructed, should the user navigate away and then return to that target. Add the possibilities of multiple modeless windows or modal popup windows, or even multiple instances of the same view over different data (say a file or a database row) within your nav hierarchy, and the right approach could get very muddy. It should still be possible to enumerate the common approaches, provide descriptions and examples, and perhaps craft a hybrid service which can reconfigure to cover all or most of these. Not easy, but no reason to wring one's hands and say it's simply impossible or a bad idea.

And special cases are the spice of life. Consider a file or internet browser - you will navigate to a new file or URL, perhaps reusing the same app window and xaml view, perhaps opening a new window, perhaps opening a new tab within the same view, or perhaps using a different view altogether if the content type is different or the reason for navigating differs (say from a read content view to a save-as content view or an edit content view).

Another quirk - you might want multiple navigation targets within a single view+viewmodel instance. In different tab selections or list positions or map coordinates, to name a few examples.

CodingOctocat commented 9 months ago

@Sergio0694 If the Frame is defined in App.xaml.cs, how is it rendered in xmal? If I have a TabControl where each TabItem is a Frame, how do I implement it?

IlyaNavodkin commented 8 months ago

I'll put together a minimal sample and post it here as a reference, at least for now. Will update in a bit 😄

EDIT: alright, so here's a few examples. One of the things I mentioned before was that there's really a number of ways to go about doing this, and people might want to tweak the implementation depending on their specific needs. For example, some developers might not be interested in having a platform-agnostic backend, so they might want to add framework-specific types directly on the navigation service (for instance, by passing the page type directly). Others might want to abstract this instead, so in that case you might want to have a service implementation that internally stores a dictionary of sorts to map viewmodel types to page types, and then perform the navigation directly. Some might want a more "pattern oriented" approach, for instance using some kind of standard naming practice for viewmodels, etc.

Here's the simples possible approach for this, with a framework-specific solution (wouldn't recommend this):

// Navigation interface
public interface INavigationService
{
    bool CanGoBack { get; }
    void GoBack();
    void Navigate<T>(object args = null);
}

// UWP navigation service
public class NavigationService : INavigationService
{
    private readonly Frame frame;

    public NavigationService(Frame frame)
    {
        this.frame = frame;
    }

    public bool CanGoBack => this.frame.CanGoBack;

    public void GoBack() => this.frame.GoBack();

    public void Navigate<T>(object args)
    {
        this.frame.Navigate(typeof(T), args);
    }
}

And then you'd use it something like this, for instance (eg. in a single-frame application):

// ...This is from App.xaml.cs, as usual
rootFrame = new Frame();

// Configure your services somewhere after that point, and add the navigation service too
Ioc.Default.ConfigureServices(
    new ServiceCollection()
    .AddSingleton<INavigationService>(new NavigationService(rootFrame))
    .BuildServiceProvider());

// ...Then whenever you need to navigate, you can do:
Ioc.Default.GetRequiredService<INavigationService>().Navigate<SomePage>();

// ...Or, using dependency injection in your viewmodels
this.navigationService.Navigate<SomePage>();

A better approach could be, for instance, to have the navigation service store a mapping of viewmodel types:

public class NavigationService : INavigationService
{
    private readonly Dictionary<Type, Type> viewMapping = new()
    {
        [typeof(MainPageViewModel)] = typeof(MainPage),
        // Other viewmodel types...
    };

    private readonly Frame frame;

    public NavigationService(Frame frame)
    {
        this.frame = frame;
    }

    public bool CanGoBack => this.frame.CanGoBack;

    public void GoBack() => this.frame.GoBack();

    public void Navigate<T>(object args)
    {
        this.frame.Navigate(this.viewMapping[typeof(T)], args);
    }
}

So that then you could use the navigation service from a framework-agnostic viewmodel, by just doing:

this.navigationService.Navigate<SomeViewModel>();

And the platform specific service would look up the target page type automatically.

This is just the general idea, as I've mentioned there could be plenty of other customizations for this, like:

  • Having APIs to add/update the mapped types after the instantiation of the service
  • Having some APIs to control other navigation parameters, reset the stack, etc.
  • Custom navigation types, eg. across multiple navigation frames (I do this in my apps, for instance), etc.

We could definitely add a sample docs page for this in the future though 😄

Good day!

Are you sure this will work correctly? After all, you need to insert either a Uri or a Page Instance into the Navigate - otherwise it will output the full type name to the view (override ToString) image

        <Frame Grid.Row="1" NavigationUIVisibility="Visible" x:Name="MainFrame"/>