Ξ 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
Page
implementsIBasePage<INotifyPropertyChanged>
, the generic argument is aViewModel
type - Every
ViewModel
must have a parameterless constructor and implementINotifyPropertyChanged
(if onlyPage
has to receive messages) orIBaseMessagable
(if bothPage
andViewModel
have 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
PageFactory
property inPage
orViewModel
(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 saveOrReplaceInCache
argument).GetMessagablePageAsNewInstance<TViewModel>()
- Creates a new Page Instance with ViewModel messaging support. New instance can replace existing cached Page instance (bool saveOrReplaceInCache = false
method 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. Iffalse
is 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
HomePage
is created and put intoPageFactory
cacheHomeViewModel
sends "WarningMessage" message toDetailsViewModel
(with warning messagestring
argument)DetailsViewModel
receives and handles received message (sets text to "When you lock this page, you’ll not be able to open it again")DetailsPage
is 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
DetailsViewModel
sends "DetailsPageAccess" message toHomeViewModel
withbool
argument set tofalse
HomeViewModel
receives receives and handles received message (disables button command and sets text which is shown insideHomePage
)DetailsPage
is popped and forced to be removed fromPageFactory
cache- "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!