Recently, Prism for Xamarin.Forms 6.2.0 has been released with many notable improvements including a new bootstrapping process, AutoWireViewModel behaviour changes, deep-linking support, modularity and Prism template pack enhancements (full release notes available here).
Today, I fired up Visual Studio to have a play with this new version and decided to try the Xamarin.Forms support for Prism Modules: this is a very useful feature which allows to separate clearly the various parts of the application and improve quality, extensibility and readability of the code.
After downloading the new template pack, I created a new solution selecting New Project => Templates => Visual C# => Prism => Forms => Prism Unity App:
The new wizard is very useful and permits to select the platforms to target in the project: I selected Android, iOS and UWP and the project was generated targeting the three platforms with a shared PCL. NuGet packages were already updated to the latest version so no need for further actions.
While exploring the new project structure and the new modularization stuff, I decided to create a new Xamarin.Forms portable class library containing a module with a single View/ViewModel (SamplePage / SamplePageViewModel) visualised when a user interacts with a button on the home page.
The new module required the definition of the following class implementing the Prism IModule interface:
using Microsoft.Practices.Unity; using Prism.Modularity; using Prism.Unity; using UsingModules.SampleModule.Views; using Xamarin.Forms.Xaml; [assembly: XamlCompilation(XamlCompilationOptions.Compile)] namespace UsingModules.SampleModule { public class SampleModule : IModule { private readonly IUnityContainer _unityContainer; public SampleModule(IUnityContainer unityContainer) { _unityContainer = unityContainer; } public void Initialize() { _unityContainer.RegisterTypeForNavigation<SamplePage>(); } } }
To keep the logic separated from the rest of the app, I decided to register the navigation type for SamplePage inside the Initialize() method of the module which will be triggered when the module loads.
I also applied Xamarin.Forms XAML compilation to the module to improve performance, which is always great to have ;)
[assembly: XamlCompilation (XamlCompilationOptions.Compile)]
It's worth noting that in this new Prism release the default value for the attached property ViewModelLocator.AutowireViewModel is set to true, so we can omit it and the framework will automatically associate SampleViewModel as the BindingContext for the view:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="UsingModules.SampleModule.Views.SamplePage" Title="SamplePage"> <StackLayout HorizontalOptions="Center" VerticalOptions="Center"> <Label Text="{Binding Title}" /> </StackLayout> </ContentPage>
using Prism.Mvvm; using Prism.Navigation; namespace UsingModules.SampleModule.ViewModels { public class SamplePageViewModel : BindableBase, INavigationAware { private string _title; public string Title { get { return _title; } set { SetProperty(ref _title, value); } } public void OnNavigatedFrom(NavigationParameters parameters) { } public void OnNavigatedTo(NavigationParameters parameters) { Title = "Sample Page from a Prism module"; var parameterName = "par"; if (parameters.ContainsKey(parameterName)) Title += " - parameter: " + (string)parameters[parameterName]; } } }
I then explored the new breaking changes for the bootstrapping process: the application class now needs to inherit from the PrismApplication class and two new virtual methods OnInitialized() and RegisterTypes() permit respectively to specify the implementation for navigating to the home page and registering the known types / ViewModel's for navigation:
using System; using Prism.Modularity; using Prism.Unity; using UsingModules.Views; using Xamarin.Forms.Xaml; [assembly: XamlCompilation(XamlCompilationOptions.Compile)] namespace UsingModules { public partial class App : PrismApplication { public App(IPlatformInitializer initializer = null) : base(initializer) { } protected override void OnInitialized() { InitializeComponent(); var navigationPage = "MainNavigationPage/MainPage"; NavigationService.NavigateAsync($"{navigationPage}?title=Hello%20from%20Xamarin.Forms"); } protected override void RegisterTypes() { Container.RegisterTypeForNavigation<MainNavigationPage>(); Container.RegisterTypeForNavigation<MainPage>(); } protected override void ConfigureModuleCatalog() { Type sampleModuleType = typeof(SampleModule.SampleModule); ModuleCatalog.AddModule( new ModuleInfo() { ModuleName = sampleModuleType.Name, ModuleType = sampleModuleType, InitializationMode = InitializationMode.OnDemand }); } } }
The third overridden method, ConfigureModuleCatalog(), informs the app to initialize the catalog with the module we created previously and set the initialization mode to OnDemand which means the module is not activated when the application starts, but it must load explicitly. This feature is particularly useful in cases in which some functionalities of the app must initialise after some other requirements like, for instance, successful authentication or applications extended via modules.
The sample view was in place, so I proceeded with the implementation of the HomePage: I wrapped the existing one in a NavigationPage to allow the correct back stack and then created two commands for initializing the module and navigating to the SamplePage defined previously:
<?xml version="1.0" encoding="utf-8" ?> <NavigationPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="UsingModules.Views.MainNavigationPage" Title="MainPage"> </NavigationPage>
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="UsingModules.Views.MainPage" Title="MainPage"> <StackLayout HorizontalOptions="Center" VerticalOptions="Center"> <Label Text="{Binding Title}" /> <Button Command="{Binding LoadSampleModuleCommand}" Text="Load Sample Module" /> <Button Command="{Binding NavigateToSamplePageCommand}" Text="Navigate to Sample Page" /> </StackLayout> </ContentPage>
and the corresponding ViewModel:
using System.Windows.Input; using Prism.Commands; using Prism.Modularity; using Prism.Mvvm; using Prism.Navigation; namespace UsingModules.ViewModels { public class MainPageViewModel : BindableBase, INavigationAware { private readonly IModuleManager _moduleManager; private readonly INavigationService _navigationService; public MainPageViewModel(IModuleManager moduleManager, INavigationService navigationService) { _moduleManager = moduleManager; _navigationService = navigationService; LoadSampleModuleCommand = new DelegateCommand(LoadSampleModule, ()=>!IsSampleModuleRegistered) .ObservesProperty(()=>IsSampleModuleRegistered); NavigateToSamplePageCommand = new DelegateCommand(NavigateToSamplePage, ()=>IsSampleModuleRegistered) .ObservesProperty(()=>IsSampleModuleRegistered); } private bool _isSampleModuleRegistered; public bool IsSampleModuleRegistered { get { return _isSampleModuleRegistered; } set { SetProperty(ref _isSampleModuleRegistered, value); } } private string _title; public string Title { get { return _title; } set { SetProperty(ref _title, value); } } public ICommand LoadSampleModuleCommand { get; set; } public ICommand NavigateToSamplePageCommand { get; set; } private void NavigateToSamplePage() { _navigationService.NavigateAsync("SamplePage?par=test"); } private void LoadSampleModule() { _moduleManager.LoadModule("SampleModule"); IsSampleModuleRegistered = true; } public void OnNavigatedFrom(NavigationParameters parameters) { } public void OnNavigatedTo(NavigationParameters parameters) { if (parameters.ContainsKey("title")) Title = (string)parameters["title"] + " and Prism"; } } }
The module is initialized by injecting the Prism ModuleManager and then calling the LoadModule() method:
private void LoadSampleModule() { _moduleManager.LoadModule("SampleModule"); IsSampleModuleRegistered = true; }
The navigation to the page defined in the module is performed by:
private void NavigateToSamplePage() { _navigationService.NavigateAsync("SamplePage?par=test"); }
The property IsSampleModuleRegistered permitted to control the CanExecute() for the button commands using this nice fluent syntax using ObservesProperty(()=>....) available in Prism:
NavigateToSamplePageCommand = new DelegateCommand(NavigateToSamplePage, ()=>IsSampleModuleRegistered) .ObservesProperty(()=>IsSampleModuleRegistered);
Here we go: I found the Prism implementation in this new version very stable and working well with Xamarin.Forms. The modularization capabilities are useful to write clean, maintainable and extensible apps.
The source code is available for download as part of the official Prism samples on GitHub.
Looking forward to exploring all the other capabilities available in Prism and Xamarin.Forms. Happy coding!