Ξ Page Factory MVVM library for Xamarin.Forms
The main reason for making PageFactory was that I needed a very simple to use MVVM library which would free me from implementing the same things for any Xamarin.Forms project I created all over again. Those things were Page Caching, ViewModel oriented Navigation, INotifyPropertyChanged and ICommand implementations, Messaging between Pages and ViewModels. I also wanted my ViewModels to be dependency free (not forcing any concrete class inheritance).
That’s it. It’s very simple, no dependency injections, no platform specific code - just plain PCL. What comes with it, it’s very very lightweight. Here’s a blog note about it.
Ξ Table of contents
- Minimal code to get it working
- PageFactory Basics
- PageFactory Messaging
- PageFactory Navigation
- PageFactory INotifyPropertyChanged implementation
- PageFactory ICommand implementation
- Example showing basic features
- Summary
Project site: https://github.com/daniel-luberda/DLToolkit.PageFactory
Ξ Minimal code to get it working
Install nuget package from: https://www.nuget.org/packages/DLToolkit.PageFactory.Forms/
Ξ Simple “HelloWorld” example:
1 | public class App : Application |
1 | public class HelloPage : PFContentPage<HelloViewModel> |
1 | public class HelloViewModel : BaseViewModel |
That’s all! You’ll have access to all PageFactory features!
Ξ PageFactory Basics
Ξ Pages and ViewModels
PageFactory uses Pages and ViewModels.
- Every
PageimplementsIBasePage<INotifyPropertyChanged>, the generic argument is aViewModeltype - Every
ViewModelmust have a parameterless constructor and implementINotifyPropertyChanged(if onlyPagehas to receive messages) orIBaseMessagable(if bothPageandViewModelhave to receive messages). There are no other requirements.
Ξ PageFactory helper implementations
- BaseViewModel for ViewModels. It implements
INotifyPropertyChanged,IBaseMessagable) and has PageFactory property which returns PF.Factory instance. - BaseModel for Models. It implements
INotifyPropertyChanged. - PageFactoryCommand, IPageFactoryCommand - PCL compatibile ICommand implementation.
Ξ PageFactory Factory instance
You can get PageFactory instance:
- using
PageFactoryproperty inPageorViewModel(if it inherits fromBaseViewModel) - using static class property:
PF.Factory
Some basic PageFactory methods you should know:
GetPageFromCache<TViewModel>()- Gets (or creates) cached Page instance.GetMessagablePageFromCache<TViewModel>()- Gets (or creates) cached Page instance with ViewModel messaging support.GetPageAsNewInstance<TViewModel>()- Creates a new Page Instance. New instance can replace existing cached Page instance (bool saveOrReplaceInCacheargument).GetMessagablePageAsNewInstance<TViewModel>()- Creates a new Page Instance with ViewModel messaging support. New instance can replace existing cached Page instance (bool saveOrReplaceInCache = falsemethod parameter).
Cache can hold only one instance of ViewModel of the same type (with its Page). You can remove cache for a ViewModel type or replace it with another instance.
Ξ Fluent methods
ViewModels and Pages (IBasePage<INotifyPropertyChanged>) have some fluent style extensions. Instead:
1 | var page = PageFactory.GetMessagablePageAsNewInstance<HomeViewModel>(); |
You can use fluent style (I personally prefer it that way):
1 | PageFactory.GetMessagablePageAsNewInstance<HomeViewModel>() |
Ξ PageFactory Messaging
All PageFactory Pages have messaging enabled. If you also want ViewModel to receive messages it must implement IBaseMessagable interface (BaseViewModel does it).
Ξ Receiving messages
To receive messages just override PageFactoryMessageReceived method (either on Page or ViewModel):
1 | public override void PageFactoryMessageReceived(string message, object sender, object arg) |
Ξ Sending messages
To send messages use:
- PageFactory static methods:
SendMessageByPage,SendMessageByViewModel,SendMessageToCached - Page extension methods:
SendMessageToPageAndViewModel,SendMessageToViewModel,SendMessageToPage
1 | PageFactory.GetMessagablePageAsNewInstance<HomeViewModel>() |
1 | PageFactory.SendMessageToCached<HomeViewModel>( |
Ξ PageFactory Navigation
Ξ Navigating
To navigate use this methods:
- PageFactory static methods:
PushPageAsync,PopPageAsync,InsertPageBefore,RemovePage,PopPagesToRootAsync,SetNewRootAndReset - Page extension methods:
PushPage,PopPage,InsertPageBefore,RemovePage,PopPagesToRoot,SetNewRootAndReset
1 | var page = PageFactory.GetPageAsNewInstance<DetailsPage>().PushPage(); |
1 | var page = PageFactory.GetPageAsNewInstance<DetailsPage>(); |
Ξ Navigation interception
You can intercept navigation. Just override one of this Page methods:
PageFactoryPushed,PageFactoryPopped,PageFactoryRemoved,PageFactoryInserted- called after successful navigationPageFactoryPushing,PageFactoryPopping,PageFactoryRemoving,PageFactoryInserting- called before navigation. Iffalseis returned, navigation will be cancelledPageFactoryRemovingFromCache- called when the page is being removed from cache
1 | public override void PageFactoryPopped() |
1 | public override bool PageFactoryPushing() |
Ξ PageFactory INotifyPropertyChanged implementation
Xamarin.Forms apps extensively use bindings. Because of that, every ViewModel should implement INotifyPropertyChanged interface. If you inherit from BaseViewModel class you can use its INotifyPropertyChanged implementation methods.
If you would like to use backing field:
1 | string someExampleProperty; |
If you don’t want to use backing field you can use (It will store field value in internal Dictionary):
1 | public string SomeExampleProperty |
If you need to notify additional properties when your property changes, you can also do it with:
1 | string someExampleProperty; |
or (It will store field value in internal Dictionary):
1 | public string SomeExampleProperty |
You can also use Fody INotifyPropertyChanged (https://github.com/Fody/PropertyChanged) and just write:
1 | public string SomeExampleProperty { get; set; } |
Don’t forget to add “FodyWeavers.xml” file to you project (BuildAction set to Content):
1 | <?xml version="1.0" encoding="utf-8" ?> |
And that’s it! Fody will auto-implement all INotifyPropertyChanged classes!
Ξ PageFactory ICommand implementation
1 | public TestViewModel() |
Then you could use it (XAML example):
1 | <Button Text="Execute TestWithoutParamCommand" |
Output after button tap: TestWithoutParamCommand executed (will always execute)
1 | <Button Text="Execute TestWithParamCommand" |
Output after button tap: TestWithParamCommand executed with param=someParameterValue
1 | <Button Text="Execute TestWithParamCommand" |
Output after button tap: Won’t execute
To notify command that CanExecute changed use PageFactory RaiseCanExecuteChanged(); method.
Ξ Example showing basic features
Ξ Flow:
App starts up ➡ User taps "Open Details Page" button ➡ User taps "Lock access & return to HomePage" button
Ξ Details:
- App starts up
- "Open Details Page" button is enabled
- "DetailsPage is accessible" text is shown inside
HomePage
- User taps "Open Details Page" button
HomePageis created and put intoPageFactorycacheHomeViewModelsends "WarningMessage" message toDetailsViewModel(with warning messagestringargument)DetailsViewModelreceives and handles received message (sets text to "When you lock this page, you’ll not be able to open it again")DetailsPageis pushed- Received "When you lock this page, you’ll not be able to open it again" text is shown inside
DetailsPage
- User taps "Lock access & return to HomePage" button
DetailsViewModelsends "DetailsPageAccess" message toHomeViewModelwithboolargument set tofalseHomeViewModelreceives receives and handles received message (disables button command and sets text which is shown insideHomePage)DetailsPageis popped and forced to be removed fromPageFactorycache- "DetailsPage is locked" text is shown inside
HomePage
Ξ Code:
1 | public class HomeViewModel : BaseViewModel |
1 | public class HomePage : PFContentPage<HomeViewModel> |
1 | public class DetailsViewModel : BaseViewModel |
1 | public class DetailsPage : PFContentPage<DetailsViewModel> |
Ξ Summary
That’s all for now. Feel free to test it out and notify me about any errors / your suggestions. Thanks!