Skip to content

couchbaselabs/Robo.Mvvm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Robo.Mvvm

The purpose of these projects are to simplify the creation simple/prototype Xamarin.Forms apps.

Again, these projects include a small set of functionality with the overall function being to create simple Xamarin.Forms apps that adhere to the Model-View-ViewModel (MVVM) design pattern.

(Much like how a simple robo..t exists to perform a few, targeted functions. Yea, see what I did there?)

Table of Contents

  1. Available Platforms
  2. Project Summary
  3. Getting Started
    1. Grab Dat Nuget!
    2. Initializing
  4. Coupling Pages to ViewModels
    1. Pages
    2. ViewModels
  5. Navigation
  6. Dependency Injection & Service Location
  7. Sample
  8. Contribute

Available Platforms

Platform Version Supported
Android MonoAndroid81 YES
iOS Xamarin.iOS10 YES
UWP Win10, v. 1809 YES
MAC Xamarin.Mac20 NO
WatchOS Xamarin.WatchOS10 PENDING
.NET Standard 2.0 YES

Project Summary

  • Robo.Mvvm is a project that contains a few classes/enumerations/etc. to help quickly spin up a platform agnostic Model-View-ViewModel (MVVM) architectural design approach for an application.

  • Robo.Mvvm.Forms is a project that uses the Robo.Mvvm project, and provides a set of Xamarin.Forms specific class/object extensions that make creating prototype and simple Xamarin.Forms apps painless.

    High-level features:

    • View-ViewModel coupling (with automatic setting of BindingContext)
    • ViewModel to ViewModel navigation
    • Built-in IoC via depenedency injection (or generic service location depending on what you're into) using ServiceContainer located in the Robo.Mvvm namespace.
    • Auto-registration of View-ViewModel relationships (Note: this can also be done manually using the previously mentioned ServiceContainer)

Getting Started

Grab Dat Nuget!

Yes, there's a nuget for this! In fact, there are two:

  1. Robo.Mvvm GitHub release

  2. Robo.Mvvm.Forms GitHub release

So, you may be wondering, which one do I use and where? Well, there's a simple answer to this; if the project you're adding it to needs the Xamarin.Forms Nuget then you'll need to reference both Robo.Mvvm and Robo.Mvvm.Forms. If not, then you'll just need to include Robo.Mvvm.

Pro Tip: Remember that two important functions of the MVVM pattern are

  1. Maximize reuse which helps...(see point 2)
  2. Remain completely oblivious to the anything "View Level". This includes packages like Xamarin.Forms.

So, it's best to separate your Xamarin.Forms app/view level code (i.e. ContentPage, ContentView, Button, etc.) from your ViewModels. At the very least, in separate projects. <./rant>

Initializing

Once the Nuget packages have been installed you will need to initialize Robo.Mvvm.Forms. Add the following line to App.xaml.cs (ideally in the constructor):

public App()
{
    InitializeComponent();

    // Add this line!
    Robo.Mvvm.Forms.App.Init<RootViewModel>(GetType().Assembly);

    // Where "RootViewModel" is the ViewModel you want to be your MainPage
}

This accomplishes two things:

  1. Registers and couples the Views to ViewModels
  2. The Generic () assigned to the Init method establishes the MainPage (and coupled ViewModel).

Coupling Pages to ViewModels

Pages

All Base page perform two main operations upon instantiation:

  1. Set the BindingContext to the appropriate ViewModel received via generic.
  2. Executes the InitAsync method of the instantiated ViewModel. This is good for functionality you'd like executed upon page creation. InitAsync is optional - it exists as a virtual method in the base viewmodel.

BaseContentPage

Inherit from BaseContentPage from all ContentPage implementations.

XAML
<?xml version="1.0" encoding="UTF-8"?>
<pages:BaseContentPage 
    xmlns="http://xamarin.com/schemas/2014/forms" 
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
    xmlns:pages="clr-namespace:Robo.Mvvm.Forms.Pages;assembly=Robo.Mvvm.Forms"
    xmlns:vm="clr-namespace:PrototypeSample.Core.ViewModels;assembly=PrototypeSample.Core"
    x:TypeArguments="vm:ViewModel1"
    x:Class="PrototypeSample.Pages.ContentPage1"
    Title="Page 1">
    <pages:BaseContentPage.Content>
           
        <!-- Content here -->
                
    </pages:BaseContentPage.Content>
</pages:BaseContentPage>

Key takeaways:

  1. Change ContentPage to pages:BaseContentPage ('pages' can be whatever you name it - see #2.1 below)
  2. Include XML namespaces (xmlns) declarations for
    1. Robo.Mvvm.Forms.Pages
    2. The namespace where the ViewModel that you want to bind to this page.
  3. Add the TypeArgument for the specific ViewModel you want to bind to this page.
CS

The ContentPage implementation just needs to inherit from BaseContentPage and provide the ViewModel to be bound to the Page.

public partial class ContentPage1 : BaseContentPage<ViewModel1>

BaseMasterDetailPage

XAML
<?xml version="1.0" encoding="utf-8"?>
<pages:BaseMasterDetailPage 
    xmlns="http://xamarin.com/schemas/2014/forms" 
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
    xmlns:pages="clr-namespace:Robo.Mvvm.Forms.Pages;assembly=Robo.Mvvm.Forms"
    xmlns:vm="clr-namespace:PrototypeSample.Core.ViewModels;assembly=PrototypeSample.Core"
    x:TypeArguments="vm:RootViewModel"
    x:Class="PrototypeSample.Pages.RootPage">

</pages:BaseMasterDetailPage>

Key takeaways: See ContentPage XAML.

CS

The MasterDetailPage implementation just needs to inherit from BaseMasterDetailPage and provide the BaseMasterDetailViewModel to be bound to the Page.

public partial class RootPage : BaseMasterDetailPage<RootViewModel>

BaseTabbedPage

XAML
<?xml version="1.0" encoding="utf-8"?>
<pages:BaseTabbedPage 
    xmlns="http://xamarin.com/schemas/2014/forms" 
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
    xmlns:pages="clr-namespace:Robo.Mvvm.Forms.Pages;assembly=Robo.Mvvm.Forms"
    xmlns:vm="clr-namespace:PrototypeSample.Core.ViewModels;assembly=PrototypeSample.Core"
    x:TypeArguments="vm:CollectionViewModel"
    x:Class="PrototypeSample.Pages.TestTabbedPage"
    Title="Tabbed Page">
</pages:BaseTabbedPage>

Key takeaways: See ContentPage XAML.

CS

The TabbedPge implementation just needs to inherit from BaseTabbedPage and provide the BaseCollectionViewModel to be bound to the Page.

public partial class TestTabbedPage : BaseTabbedPage<CollectionViewModel>

ViewModels

BaseNotify

BaseNotify is an abstract class that implements INotifyPropertyChanged, and provides implementations for:

PropertyChanged

public event PropertyChangedEventHandler PropertyChanged;

SetPropertyChanged

public void SetPropertyChanged(string propertyName)
{ ... }

protected virtual bool SetPropertyChanged<T>(ref T currentValue, T newValue, [CallerMemberName] string propertyName = "")
{ ... }

BaseViewModel

BaseViewModel inherits from BaseNotify.

You can inherit from the abstract class BaseViewModel for all navigation enabled ViewModels, and ViewModels you want to bind to BaseContentPage. Note that you are not limited to only binding to BaseContentPage

public class ViewModel1 : BaseViewModel

Properties available:

  • IsBusy (bool)

Methods available:

  • InitAsync: a virtual method that returns a task. This method is executed upon page creation.
  • GetViewModel: returns an instantiated ViewModel via generic.

BaseMasterDetailViewModel

Inherit from abstract class BaseMasterDetailViewModel for ViewModels you want to bind to a BaseMasterDetailPage.

public class RootViewModel : BaseMasterDetailViewModel
{
    public RootViewModel(INavigationService navigationService) : base(navigationService)
    {
        var menuViewModel = GetViewModel<MenuViewModel>();
        menuViewModel.MenuItemSelected = MenuItemSelected;

        Master = menuViewModel;
        
        Detail = GetViewModel<CollectionViewModel>();
    }

    void MenuItemSelected(BaseViewModel viewModel) => SetDetail(viewModel);
}

BaseMasterDetailViewModel inherits from BaseViewModel, and because of this all of the properties/methods available in BaseViewModel are also available in BaseMasterDetailViewModel.

Addition properties available:

  • Master (BaseViewModel)
  • Detail (BaseViewModel)

Additional methods available:

  • SetDetail: allows you to set the Detail ViewModel.

BaseCollectionViewModel

Inherit from the abstract class BaseCollectionViewModel for ViewModels you want to bind to a BaseTabbedDetailPage.

public class CollectionViewModel : BaseCollectionViewModel

BaseCollectionViewModel inherits from BaseViewModel, and because of this all of the properties/methods available in BaseViewModel are also available in BaseCollectionViewModel.

Addition properties available:

  • EnableNavigation (bool) - defaulted to true - determines if the page will exist within navigation stack (page)
  • SelectedIndex (int)
  • SelectedViewModel (BaseViewModel)
  • ViewModels (List<BaseViewModel>)

Navigation

Navigation from one ViewModel to another is very simple. Below are samples, using 'Navigation' for an 'INavigationService' resolution, we are able to perform several actions.

Push

await Navigation.PushAsync<ViewModel>();
await Navigation.PushAsync(GetViewModel<ViewModel>());

Push Modal

await Navigation.PushModalAsync<ViewModel>();
await Navigation.PushModalAsync(GetViewModel<ViewModel>());

Pop

await Navigation.PopAsync();

Set Root

await Navigation.SetRoot<ViewModel>();
await Navigation.SetRoot(GetViewModel<ViewModel>());

Dependency Injection & Service Location

Simple Dependency Injection is exposed through a class called ServiceContainer that merely wraps Simple Injector.

Registration

Service.Container.Resolve<IAlertService>();

Resolving Manually

var alertService = ServiceContainer.Register<IAlertService>(new AlertService());

Resolving via Constructor Injection

public ViewModel1(IAlertService alertService)

Sample

Please feel free to clone this repository, and run the sample located here. The sample app contains a demonstration of all the major features included in Robo.Mvvm and Robo.Mvvm.Forms.

Contribute

Please feel free to contribute to this project! I certainly need all the help I can get, and welcome all PR's, issues, questions, etc. You can also contact me at [email protected] and on Twitter.