Implementing custom navigation in FreshMvvm for Xamarin.Forms

In past posts I’ve already introduced the FreshMvvm framework for Xamarin.Forms. If you haven’t taken a look I suggest this post to give you an overview of FreshMvvm.

I use FreshMvvm in the majority of my projects. I love it’s simplicity but most of all it’s flexibility. The Framework is Interface based and designed to be flexible. One of the most important areas of an app is the Navigation, while FreshMvvm has some great built in Navigation abilities sometimes we might need to extend this and create our own Navigation Service.

FreshMvvm comes with an interface named IFreshNavigationService which allows any type of custom Navigation. The steps to implement a custom navigation service in FreshMvvm are as follows:

1) Implement the IFreshNavigationService interface

2) Register the implementation in the FreshMvvm IOC container

Let’s start by taking a look at the interface.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;
using Xamarin.Forms;
using System.Threading.Tasks;
 
namespace FreshMvvm
{
    public interface IFreshNavigationService
    {
        Task PopToRoot(bool animate = true);
 
        Task PushPage (Page page, FreshBasePageModel model, bool modal = false, bool animate = true);
 
        Task PopPage (bool modal = false, bool animate = true);
    }
}

Let’s begin with a simple NavigationService using a basic NavigationPage. We can name this class SimpleCustomNavigationService, inherit from NavigationPage and implement the IFreshNavigationService interface.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class SimpleCustomNavigationService : NavigationPage, IFreshNavigationService
{
    public SimpleCustomNavigationService (Page page) : base (page)
    {
    }
 
    public async Task PopToRoot (bool animate = true)
    {
        await Navigation.PopToRootAsync (animate);
    }
 
    public async Task PushPage (Page page, FreshMvvm.FreshBasePageModel model, bool modal = false, bool animate = true)
    {
        if (modal)
            await Navigation.PushModalAsync (page, animate);
        else
            await Navigation.PushAsync (page, animate);
    }
 
    public async Task PopPage (bool modal = false, bool animate = true)
    {
        if (modal)
            await Navigation.PopModalAsync (animate);
        else
            await Navigation.PopAsync (animate);
    }
}

Now that we have our Navigation service we just need 1) create the navigation service 2) register the navigation service in the IOC container 3) display the navigation service in the main page.

1
2
3
4
5
6
7
8
9
10
11
//Get the first page to be displayed
var page = FreshPageModelResolver.ResolvePageModel<MainMenuPageModel> ();
 
//create our navigation service
var customNavigationService = new SimpleCustomNavigationService (page);
 
//register the Navigation service in the app, this enables us to push model to model
FreshIOC.Container.Register<IFreshNavigationService> (customNavigationService);
 
//display navigation service in main page
MainPage = customNavigationService;

So now that we’ve correctly implemented the Navigation Service, we can achieve the holy grail, with PageModel to PageModel navigation.From any of our PageModels we can now push the PageModel by using the PushPageModel<T>.  As you would expect the ‘CoreMethods’ property is also interface based making it perfect for unit testing.

1
2
3
4
5
6
7
public Command AddContact {
    get {
        return new Command (async () => {
            await CoreMethods.PushPageModel<ContactPageModel> ();
        });
    }
}

Some more complex examples

What I find people fail to understand is that using this interface you can implement any type of advanced Navigation you like. There’s a few things to understand.

1) You can use custom logic to register a Navigation Service, an example of this would be to use to different types of Navigation Services per platform or based on screensize. In this example I’m using a MasterDetail for the Phone and a TabbedNavigation for the Tablet. You can do anything you like in this case.

1
2
3
4
5
6
7
8
9
10
11
12
if (Device.Idiom == TargetIdiom.Phone) {
    var masterDetailNav = new FreshMasterDetailNavigationContainer ();
    masterDetailNav.Init ("Menu");
    masterDetailNav.AddPage<ContactListPageModel> ("Contacts", null);
    masterDetailNav.AddPage<QuoteListPageModel> ("Quotes", null);
    MainPage = masterDetailNav;
} else {
    var tabbedNavigation = new FreshTabbedNavigationContainer ();
    tabbedNavigation.AddTab<ContactListPageModel> ("Contacts", "contacts.png", null);
    tabbedNavigation.AddTab<QuoteListPageModel> ("Quotes", "document.png", null);
    MainPage = tabbedNavigation;
}

2) The Framework doesn’t care what you put into the Push and Pop methods, you can put any type of UI logic that’s required for your interface. In this example below I’m combining a MasterDetail and a TabbedPage.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/// <summary>
/// This is a sample custom implemented Navigation. It combines a MasterDetail and a TabbedPage.
/// </summary>
public class MasterTabbedNavigationService : MasterDetailPage, IFreshNavigationService
{
    FreshTabbedNavigationContainer _tabbedNavigationPage;
    Page _contactsPage, _quotesPage;
 
    public MasterTabbedNavigationService ()
    {   
        SetupTabbedPage ();
        CreateMenuPage ("Menu");
        RegisterNavigation ();
    }
 
    void SetupTabbedPage()
    {
        _tabbedNavigationPage = new FreshTabbedNavigationContainer ();
        _contactsPage = _tabbedNavigationPage.AddTab<ContactListPageModel> ("Contacts", "contacts.png");
        _quotesPage = _tabbedNavigationPage.AddTab<QuoteListPageModel> ("Quotes", "document.png");
        this.Detail = _tabbedNavigationPage;
    }
 
    protected void RegisterNavigation()
    {
        FreshIOC.Container.Register<IFreshNavigationService> (this);
    }
 
    protected void CreateMenuPage(string menuPageTitle)
    {
        var _menuPage = new ContentPage ();
        _menuPage.Title = menuPageTitle;
        var listView = new ListView();
 
        listView.ItemsSource = new string[] { "Contacts", "Quotes", "Modal Demo" };
 
        listView.ItemSelected += async (sender, args) =>
        {
 
            switch ((string)args.SelectedItem) {
            case "Contacts":
                _tabbedNavigationPage.CurrentPage = _contactsPage;
                break;
            case "Quotes":
                _tabbedNavigationPage.CurrentPage = _quotesPage;
                break;
            case "Modal Demo":
                var modalPage = FreshPageModelResolver.ResolvePageModel<ModalPageModel>();
                await PushPage(modalPage, null, true);
                break;
            default:
                break;
            }
 
            IsPresented = false;
        };
 
        _menuPage.Content = listView;
 
        Master = new NavigationPage(_menuPage) { Title = "Menu" };
    }
 
    public virtual async Task PushPage (Xamarin.Forms.Page page, FreshBasePageModel model, bool modal = false, bool animated = true)
    {
        if (modal)
            await Navigation.PushModalAsync (new NavigationPage(page), animated);
        else
            await ((NavigationPage)_tabbedNavigationPage.CurrentPage).PushAsync (page, animated); 
    }
 
    public virtual async Task PopPage (bool modal = false, bool animate = true)
    {
        if (modal)
            await Navigation.PopModalAsync ();
        else
            await ((NavigationPage)_tabbedNavigationPage.CurrentPage).PopAsync (); 
    }
 
    public virtual async Task PopToRoot (bool animate = true)
    {
        await ((NavigationPage)_tabbedNavigationPage.CurrentPage).PopToRootAsync (animate);
    }
}

The best part about all of these NavigationService’s is that you can swap them out and never have to change your PageModels.

I’ve uploaded a sample app to github here.

If you have any question please feel free to contact me. 

PS, if you find any parts that need improvements or you have a better options please email me, I’m always trying to improve this Framework and all feedback is welcome.

Thanks

Michael

 

1 Response

  1. So I”m giving Fresh MVVM a go and pretty excited about it. Have some questions though.

    How would you implement navigation using different Pages for tablet/phone but using the same underlining Page Model? Similarly, how would you handle device orientation with different Pages with same Page Model?

    Would you prefer I ask in Stack Overflow?

    Cheers,
    Andrew

Leave a Reply