Building a Cross Platform MVVM app with ReactiveUI and Xamarin.Forms.
Have you ever heard about Reactiveui ? or let’s first say Reactive Extensions (RX)?. If yes then you will be comfortable with the rest of this post, else let me just slightly introduce it to you. Reactive Extensions (RX) is a library which provides you a convenient way to declare callbacks and manage asynchronous executions in your code and it does all of this using the LinQ syntax. It helps makes your source code more readable and avoids ? code (Know what am talking about right. ?). Now Imagine you could leverage all of these with the MVVM architectural design pattern. This is what ReactiveUI permits developers to do and alot more. You can easily build MVVM apps with ReactiveUI and Xamarin.Forms, this post is all about that.
What we will be building
We will build a simple Todo Xamarin.Forms application which will have a login page. Here are the topics we will cover. This app is simple but it makes uses of several features of ReactiveUI
- Model
- View
- View Model
- Naviation
- Locator (for the View, View Model, Services).
Here is the Github repository for the app we will build. Check this great example which inspired me.
First, You need to create a Xamarin.Forms app and add the following packages to your solution.
Be the first notified about new posts
[zc4wp_za2]
Model
Since we will build a todo app, this model should be very simple.
1 2 3 4 5 6 7 8 9 10 11 | public class Todo : ReactiveObject { public string Title { get; set; } bool _isDone; public bool IsDone { get => _isDone; set => this.RaiseAndSetIfChanged(ref _isDone, value); } public bool IsEnabled => !IsDone; } |
Note that our model is inheriting from ReactiveObject which is the base class for all ViewModels in ReactiveUI, But we need it here because it implements the INotifyPropertyChanged which we need for one property of our Mode “IsDone”.
ViewModels
The ViewModel will have the properties to which the view will bind and it is the one responsible for the logic performed on the data presented. We will first make a BaseViewModel from which every view model will inherit.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public class ViewModelBase : ReactiveObject, IRoutableViewModel, ISupportsActivation { public string UrlPathSegment { get; protected set; } public IScreen HostScreen { get; protected set; } public ViewModelActivator Activator { get { return viewModelActivator; } } protected readonly ViewModelActivator viewModelActivator = new ViewModelActivator(); public ViewModelBase(IScreen hostScreen = null) { HostScreen = hostScreen ?? Locator.Current.GetService<IScreen>(); } } |
Now we will build the ViewModels for the Login Page and the Todo Page. This ViewModel will have 3 main properties bound to the UI, the Password, UserName and LoginCommands. Follow these steps.
- Create a class LoginViewModel let it inherit from ViewModelBase.
- Add two properties UserName and Password. these properties will need to notify the view when their values change so we will call the RaiseAndSetIfChanged from ReactiveObject.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | private string _userName; public string UserName { get => _userName; //Notify when property user name changes set => this.RaiseAndSetIfChanged(ref _userName, value); } private string _password; public string Password { get => _password; set => this.RaiseAndSetIfChanged(ref _password, value); } |
- Now we create a command which will serve in login the user, and we do so with the ReactiveCommand
public ReactiveCommand LoginCommand { get; private set; }
- Remember how a login screen should function, it should only let the user have access when he has correct emai and password and when his email and passwords match certain criteria (i.e it should be validated).
- Matching correct username and passwords will be done using with this service. “Don’t bother about it now, we will cover it later”.1ILogin _loginService;
- The Login command should only fire when The email and password are valid. This is when the power of ReactiveUI is demonstrated!!!!!!!!. Watch carefully.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | ///When there is a change in either the password or email, ///This is been fired and if it is validated according to ///What is shown below, the ValidLogin Property is been updated. this.WhenAnyValue(x => x.UserName, x => x.Password, (email, password) => ( ///Validate the password !string.IsNullOrEmpty(password) && password.Length > 5 ) && ( ///Validate teh email. !string.IsNullOrEmpty(email) && Regex.Matches(email, "^\\w+([-+.']\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$").Count == 1 )) .ToProperty(this, v => v.ValidLogin, out _validLogin); ///We add the logic first, describing what the login command will perfom when ran ///And we add a CamExecute clause which states that the command will be able to execute only ///If Email and Password are valid, that is, when ValidLogin has a value of true. LoginCommand = ReactiveCommand.CreateFromTask(async () => { var lg = await login.Login(_userName, _password); if (lg) { HostScreen.Router .Navigate .Execute(new ItemsViewModel()) .Subscribe(); } }, this.WhenAnyValue(x => x.ValidLogin, x => x.ValidLogin, (validLogin, valid) => ValidLogin && valid)); |
- The ValidLogin Property is what is called a OAPH this is used when a property’s value depends on another property. Declared as follows.
1 2 3 4 5 | ObservableAsPropertyHelper<bool> _validLogin; public bool ValidLogin { get { return _validLogin?.Value ?? false; } } |
Here is the full code for the Login ViewModel.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | public class LoginViewModel : ViewModelBase { ILogin _loginService; private string _userName; public string UserName { get => _userName; //Notify when property user name changes set => this.RaiseAndSetIfChanged(ref _userName, value); } private string _password; public string Password { get => _password; set => this.RaiseAndSetIfChanged(ref _password, value); } /// <summary> /// This is an Oaph Observable propperty helper, /// Which is used to determine whether a subsequent action /// Could be performed or not depending on its value /// This condition is calculated every time its value changes. /// </summary> ObservableAsPropertyHelper<bool> _validLogin; public bool ValidLogin { get { return _validLogin?.Value ?? false; } } public ReactiveCommand LoginCommand { get; private set; } public LoginViewModel(ILogin login, IScreen hostScreen = null) : base(hostScreen) { _loginService = login; ///When there is a change in either the password or email, ///This is been fired and if it is validated according to ///What is shown below, the ValidLogin Property is been updated. this.WhenAnyValue(x => x.UserName, x => x.Password, (email, password) => ( ///Validate the password !string.IsNullOrEmpty(password) && password.Length > 5 ) && ( ///Validate teh email. !string.IsNullOrEmpty(email) && Regex.Matches(email, "^\\w+([-+.']\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$").Count == 1 )) .ToProperty(this, v => v.ValidLogin, out _validLogin); ///We add the logic first, describing what the login command will perfom when ran ///And we add a CamExecute clause which states that the command will be able to execute only ///If Email and Password are valid, that is, when ValidLogin has a value of true. LoginCommand = ReactiveCommand.CreateFromTask(async () => { var lg = await login.Login(_userName, _password); if (lg) { HostScreen.Router .Navigate .Execute(new ItemsViewModel()) .Subscribe(); } }, this.WhenAnyValue(x => x.ValidLogin, x => x.ValidLogin, (validLogin, valid) => ValidLogin && valid)); } } |
The ItemsViewModel will be for the Todos which the user will use. It has several portions similar to the LoginViewModel, but here is what is new in it.
- This view model has a property called Todos which is a ReactiveList of Todo Items. This list is an improved version of Observable collections, with addintional events one of which is the capability of listening to when an item changes in the list.
- When ever a user will mark a todo as completed, the item will be sent to the bottom of the list and set as inactive on the View. Here is how this is done.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //Dont forget to set ChangeTrackingEnabled to true. Todos = new ReactiveList<Todo>() { ChangeTrackingEnabled = true }; ///Lets detect when ever a todo Item is marked as done ///IF it is, it is sent to the bottom of the list ///Else nothing happens Todos.ItemChanged.Where(x => x.PropertyName == "IsDone" && x.Sender.IsDone) .Select(x => x.Sender) .Subscribe(x => { if (x.IsDone) { Todos.Remove(x); Todos.Add(x); } }); |
Here is the complete code for this ViewModel.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | public class ItemsViewModel : ViewModelBase { /// <summary> /// Reactive List https://reactiveui.net/docs/handbook/collections/reactive-list /// </summary> ReactiveList<Todo> _todos; public ReactiveList<Todo> Todos { get => _todos; set => this.RaiseAndSetIfChanged(ref _todos, value); } private Todo _selectedTodo; public Todo SelectedTodo { get => _selectedTodo; set => this.RaiseAndSetIfChanged(ref _selectedTodo , value); } private ObservableAsPropertyHelper<bool> _canAdd; public bool CanAdd => _canAdd?.Value ?? false; private string _todoTitl; public string TodoTitle { get { return _todoTitl; } set {this.RaiseAndSetIfChanged(ref _todoTitl, value); } } public ReactiveCommand AddCommand { get; private set; } public ItemsViewModel(IScreen hostScreen = null) : base(hostScreen) { this.WhenAnyValue(x => x.TodoTitle, title => !String.IsNullOrEmpty(title)).ToProperty(this, x => x.CanAdd, out _canAdd); AddCommand = ReactiveCommand.CreateFromTask( () => { Todos.Add(new Todo() { Title = TodoTitle }); TodoTitle = string.Empty; return Task.CompletedTask; }, this.WhenAnyValue(x => x.CanAdd, canAdd => canAdd && canAdd)); //Dont forget to set ChangeTrackingEnabled to true. Todos = new ReactiveList<Todo>() { ChangeTrackingEnabled = true }; Todos.Add(new Todo { IsDone = false, Title = "Go to Sleep" }); Todos.Add(new Todo { IsDone = false, Title = "Go get some dinner" }); Todos.Add(new Todo { IsDone = false, Title = "Watch GOT" }); Todos.Add(new Todo { IsDone = false, Title = "Code code and code!!!!" }); ///Lets detect when ever a todo Item is marked as done ///IF it is, it is sent to the bottom of the list ///Else nothing happens Todos.ItemChanged.Where(x => x.PropertyName == "IsDone" && x.Sender.IsDone) .Select(x => x.Sender) .Subscribe(x => { if (x.IsDone) { Todos.Remove(x); Todos.Add(x); } }); } } |
The View, Navigation And Locator
The Views all inherit from the ReactiveContentPage We will create a base view from which every view will inherit.
1 2 3 | public class ContentPageBase<TViewModel> : ReactiveContentPage<TViewModel> where TViewModel : class { } |
- Create a LoginPage and ItemsPage, and let it inherit from the ContentPageBase we just create.
- Let the IViewModel be the ViewModel corresponding to each View.
- Change Xaml and let the Page be of type ContentPageBase and inside the Xaml, don’t forget to set the TypeArguments in Xaml. Here is the full code for each View.
Login Page
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <?xml version="1.0" encoding="utf-8" ?> <ui:ContentPageBase xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="ReactiveUIDemo.Views.LoginPage" xmlns:rxui="clr-namespace:ReactiveUI.XamForms;assembly=ReactiveUI.XamForms" xmlns:viewModels="clr-namespace:ReactiveUIDemo.ViewModel" xmlns:views="clr-namespace:ReactiveUIDemo.Views" xmlns:ui="clr-namespace:ReactiveUIDemo.Views" x:TypeArguments="viewModels:LoginViewModel" > <ContentPage.Content> <StackLayout> <Label Text="Login" FontSize="Large" FontAttributes="Bold" HorizontalOptions="Center" VerticalOptions="Center" Margin="10"/> <Entry Placeholder="Email" Text="{Binding UserName}" Margin="10, 50, 10, 0"/> <Entry Placeholder="Password" IsPassword="True" Text="{Binding Password}" Margin="10"/> <Button HorizontalOptions="Center" Margin="10" Text="Login" Command="{Binding LoginCommand}"/> </StackLayout> </ContentPage.Content> </ui:ContentPageBase> |
Items Page
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | <?xml version="1.0" encoding="utf-8" ?> <ui:ContentPageBase xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:viewModels="clr-namespace:ReactiveUIDemo.ViewModel" xmlns:views="clr-namespace:ReactiveUIDemo.Views" xmlns:ui="clr-namespace:ReactiveUIDemo.Views" x:TypeArguments="viewModels:ItemsViewModel" x:Class="ReactiveUIDemo.Views.ItemsPage"> <StackLayout> <ListView x:Name="MyListView" ItemsSource="{Binding Todos}" SelectedItem="{Binding SelectedTodo}" CachingStrategy="RecycleElement"> <!--Custom View Cells--> <ListView.ItemTemplate> <DataTemplate> <ViewCell IsEnabled="{Binding IsEnabled}"> <StackLayout Orientation="Horizontal"> <Label Text="{Binding Title}" Style="{DynamicResource ListItemTextStyle}" HorizontalOptions="Start"/> <Switch IsToggled="{Binding IsDone}" HorizontalOptions="End" IsEnabled="{Binding IsEnabled}"/> </StackLayout> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> <StackLayout Orientation="Horizontal"> <Entry Text="{Binding TodoTitle}" HorizontalOptions="FillAndExpand"/> <Button Text="Add" HorizontalOptions="End" Command="{Binding AddCommand}"/> </StackLayout> </StackLayout> </ui:ContentPageBase> |
The Locator, where our ViewModels are instantiated and Views are located according to their corresponding ViewModels will be our AppBootstrapper.
- It posseses a property called Router, which is used for navigation.
- It uses the Locator from splat, the nuget package we installed earlier, this helps as a container.
- In the constructor, we set the page to navigate to as the first screen as follows. That is how navigation is done in for the registered Views and ViewModels.
1 2 3 4 5 | this .Router .NavigateAndReset .Execute(new LoginViewModel(Locator.CurrentMutable.GetService<ILogin>())) .Subscribe(); |
Here is the whole code for our AppBootstrapper
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | public class AppBootsrapper : ReactiveObject, IScreen { public RoutingState Router { get; protected set; } public AppBootsrapper() { Router = new RoutingState(); ///You much register This as IScreen to represent your app's main screen Locator.CurrentMutable.RegisterConstant(this, typeof(IScreen)); //We register the service in the locator Locator.CurrentMutable.Register(() => new LoginService(), typeof(ILogin)); //Register the views Locator.CurrentMutable.Register(() => new LoginPage(), typeof(IViewFor<LoginViewModel>)); Locator.CurrentMutable.Register(() => new ItemsPage(), typeof(IViewFor<ItemsViewModel>)); this .Router .NavigateAndReset .Execute(new LoginViewModel(Locator.CurrentMutable.GetService<ILogin>())) .Subscribe(); } public Page CreateMainPage() { // NB: This returns the opening page that the platform-specific // boilerplate code will look for. It will know to find us because // we've registered our AppBootstrappScreen. return new ReactiveUI.XamForms.RoutedViewHost(); } } |
Inside your App.Xaml.cs don’t forget to add this
1 2 3 4 5 6 7 | public App () { InitializeComponent(); var bootstrapper = new AppBootsrapper(); MainPage = bootstrapper.CreateMainPage(); } |
Service
The service is a fake, created just for demo purposes but it could be any type of service which you need, here it is.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | class LoginService : ILogin { Dictionary<string, string> _userCredentials; public LoginService() { _userCredentials = new Dictionary<string, string>(); _userCredentials.Add("us@sad.com", "aaaaaaaa"); _userCredentials.Add("user2@sad.com", "Userabc123"); _userCredentials.Add("user3@sad.com", "!A@3534"); } public async Task<bool> Login(string username, string password) { if(_userCredentials.ContainsKey(username)) { return _userCredentials[username] == password; } return false; } } |
The Demo app should be functional and hopefully, this post will help you get started with this awesome Framework.
This Xamarin Forms application to help you manage your expenses and income was built entirely with Xamarin Forms and ReactiveUI. You can download and use it or play with it for free on Android and Windows 10 (Universal Windows Platform). You can get it on Playstore, or on the Microsoft Store.
Follow me on social media and stay updatedHere is the Github repository for the app we built.
Conclusion
All the simplicity and the beauty which Reactive extensions brings to us, is like a blessing. This post just covers a little bit of what you can do when you combine the power of Reactiveui and Xamarin.Forms . You can for example, decide to listen to changes on a property, then delay, and perform an action by invoking a command. You can also, subscribe to this delay to manipulate your code later. This is extremly usefull in several scenarios. In fact, just have a glance at the documentation on Reactiveui and Reactive Extensions for .Net.
If you liked this post, or it was useful to you, please ? like it, share it on twitter, facebook or other social media… in case you want to get updated on any new useful post, follow me on twitter and like my page on facebook.
Check some other very usefull Xamarin Posts here.
Build a cross platform mobile app for your chat bot here.
Follow me on social media and stay updated
theincredibleschnigges
Doumer