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?)
- Available Platforms
- Project Summary
- Getting Started
- Coupling Pages to ViewModels
- Navigation
- Dependency Injection & Service Location
- Sample
- Contribute
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 |
-
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 theRobo.Mvvm
project, and provides a set ofXamarin.Forms
specific class/object extensions that make creating prototype and simpleXamarin.Forms
apps painless.- 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 theRobo.Mvvm
namespace. - Auto-registration of View-ViewModel relationships (Note: this can also be done manually using the previously mentioned
ServiceContainer
)
Yes, there's a nuget for this! In fact, there are two:
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
- Maximize reuse which helps...(see point 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>
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:
- Registers and couples the Views to ViewModels
- The Generic () assigned to the
Init
method establishes theMainPage
(and coupled ViewModel).
All Base page perform two main operations upon instantiation:
- Set the BindingContext to the appropriate ViewModel received via generic.
- 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 avirtual
method in the base viewmodel.
Inherit from BaseContentPage
from all ContentPage
implementations.
<?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>
- Change
ContentPage
topages:BaseContentPage
('pages' can be whatever you name it - see #2.1 below) - Include XML namespaces (xmlns) declarations for
Robo.Mvvm.Forms.Pages
- The namespace where the ViewModel that you want to bind to this page.
- Add the
TypeArgument
for the specific ViewModel you want to bind to this page.
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>
<?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.
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>
<?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.
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>
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
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
- 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.
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.
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 from one ViewModel to another is very simple. Below are samples, using 'Navigation' for an 'INavigationService' resolution, we are able to perform several actions.
await Navigation.PushAsync<ViewModel>();
await Navigation.PushAsync(GetViewModel<ViewModel>());
await Navigation.PushModalAsync<ViewModel>();
await Navigation.PushModalAsync(GetViewModel<ViewModel>());
await Navigation.PopAsync();
await Navigation.SetRoot<ViewModel>();
await Navigation.SetRoot(GetViewModel<ViewModel>());
Simple Dependency Injection is exposed through a class called ServiceContainer
that merely wraps Simple Injector.
Service.Container.Resolve<IAlertService>();
var alertService = ServiceContainer.Register<IAlertService>(new AlertService());
public ViewModel1(IAlertService alertService)
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
.
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 robert.hedgpeth@couchbase.com and on Twitter.