

TODO List using Reactive UI in Xamarin Forms
source link: https://www.xamboy.com/2021/06/22/todo-list-using-reactive-ui-in-xamarin-forms/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

During the past months, I have been learning and writing about Dynamic Data and how we can use it to handle our collections in a reactive way. In this article, I will show you a full sample of how to use the Reactive UI framework in Xamarin Forms by creating a ToDo List.
“ReactiveUI is a Framework that gives you the power to build reactive, testable, and composable UI code using the MVVM pattern.”.
Let’s start!
1. Install the ReactiveUI.XamForms NuGet package
2. Install the DynamicData NuGet package
We are going to use it to handle our ToDo list.
3. Install the Sextant.XamForms package
We are going to use it to handle the page navigation.
4. Create the structure of the TODO
- Create a HomePage -> HomePageViewModel, to handle the list of TODOs.
- Create an ItemPage -> ItemViewModel, to handle adding and editing items in the TODO list.
- Create a Model -> Item. (Represents our ToDo item)
- Create an ItemManager to handle our ToDo collection.
5. Add the following classes
ViewModelBase -> Base class for all ViewModels.
namespace ReactiveToDoSample.ViewModels { public abstract class ViewModelBase : ReactiveObject, IDisposable, INavigable { protected ViewModelBase(IParameterViewStackService viewStackService) => NavigationService = viewStackService;
public abstract string Id { get; }
public virtual IObservable<Unit> WhenNavigatedFrom(INavigationParameter parameter) => Observable.Return(Unit.Default);
public virtual IObservable<Unit> WhenNavigatedTo(INavigationParameter parameter) => Observable.Return(Unit.Default);
public virtual IObservable<Unit> WhenNavigatingTo(INavigationParameter parameter) => Observable.Return(Unit.Default);
protected IParameterViewStackService NavigationService { get; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
protected virtual void Dispose(bool disposing) { if (disposing) { Subscriptions?.Dispose(); } } protected readonly CompositeDisposable Subscriptions = new CompositeDisposable(); } }
RxExceptionHandler -> Class to handle exceptions.
namespace ReactiveToDoSample { public class RxExceptionHandler : IObserver<Exception> { public void OnNext(Exception ex) { if (Debugger.IsAttached) { Debugger.Break(); }
RxApp.MainThreadScheduler.Schedule(() => { throw ex; }); }
public void OnError(Exception ex) { if (Debugger.IsAttached) { Debugger.Break(); }
RxApp.MainThreadScheduler.Schedule(() => { throw ex; }); }
public void OnCompleted() { if (Debugger.IsAttached) { Debugger.Break(); }
RxApp.MainThreadScheduler.Schedule(() => { throw new NotImplementedException(); }); } } }
These classes were taken from this Sample project.
NavigationParameterConstants -> Class for navigation constants
namespace ReactiveToDoSample { public class NavigationParameterConstants { public const string ItemId = "ItemId"; } }
6. Register Views, ViewModels and Services in the App.xaml.cs
public partial class App : Application { public App() { InitializeComponent();
RxApp.DefaultExceptionHandler = new RxExceptionHandler();
Instance.InitializeForms();
Locator .CurrentMutable .RegisterConstant<IItemManager>(new ItemManager());
Locator .CurrentMutable .RegisterNavigationView(() => new NavigationView(RxApp.MainThreadScheduler, RxApp.TaskpoolScheduler, ViewLocator.Current)) .RegisterParameterViewStackService() .RegisterView<HomePage, HomeViewModel>() .RegisterView<ItemPage, ItemViewModel>() .RegisterViewModel(() => new HomeViewModel(Locator.Current.GetService<IParameterViewStackService>(), Locator.Current.GetService<IItemManager>())) .RegisterViewModel(() => new ItemViewModel(Locator.Current.GetService<IParameterViewStackService>(), Locator.Current.GetService<IItemManager>()));
Locator .Current .GetService<IParameterViewStackService>() .PushPage<HomeViewModel>(null, true, false) .Subscribe();
MainPage = Locator.Current.GetNavigationView("NavigationView"); } }
7. Create the Item Model
It will have 3 main properties: Id, Title and a mutable IsCompleted property, which will indicate when the item has been completed.
namespace ReactiveToDoSample.Models { public class Item : ReactiveObject { public Item(string id, string title) { Id = id; Title = title; }
public string Id { get; }
public string Title { get; }
public bool IsCompleted { get => _isCompleted; set => this.RaiseAndSetIfChanged(ref _isCompleted, value); }
private bool _isCompleted; } }
8. Create the Item Manager
This manager will handle all the logic related to adding/removing/getting items.
namespace ReactiveToDoSample.Managers { public interface IItemManager { public IObservable<IChangeSet<Item, string>> ItemChanges { get; }
public Optional<Item> Get(string id);
public void AddOrUpdate(Item item);
public void Remove(Item item); } }
public class ItemManager : IItemManager { public ItemManager() { ItemChanges = _itemsCache.Connect() .RefCount(); }
public Optional<Item> Get(string id) => _itemsCache.Lookup(id);
public IObservable<IChangeSet<Item, string>> ItemChanges { get; }
public void AddOrUpdate(Item item) => _itemsCache.AddOrUpdate(item);
public void Remove(Item item) => _itemsCache.Remove(item);
private SourceCache<Item, string> _itemsCache = new SourceCache<Item, string>(item => item.Id); }
9. Create the HomePage/ViewModel
In the UI we will have a simple CollectionView and an Add button.
<?xml version="1.0" encoding="UTF-8" ?> <rxui:ReactiveContentPage x:Class="ReactiveToDoSample.Views.HomePage" xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:rxui="clr-namespace:ReactiveUI.XamForms;assembly=ReactiveUI.XamForms" xmlns:vm="clr-namespace:ReactiveToDoSample.ViewModels" x:Name="homePage" Title="Reactive ToDo" x:TypeArguments="vm:HomeViewModel"> <StackLayout Padding="20"> <CollectionView ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" SelectionMode="Single"> <CollectionView.ItemsLayout> <GridItemsLayout Orientation="Vertical" VerticalItemSpacing="10" /> </CollectionView.ItemsLayout> <CollectionView.ItemTemplate> <DataTemplate> <Frame Style="{StaticResource CardFrameStyle}"> <Frame.Triggers> <DataTrigger TargetType="Frame" Binding="{Binding IsCompleted}" Value="True"> <Setter Property="Opacity" Value="0.2" /> </DataTrigger> </Frame.Triggers> <StackLayout Orientation="Horizontal"> <CheckBox IsChecked="{Binding IsCompleted}"/> <Label Text="{Binding Title}" HorizontalOptions="FillAndExpand" FontAttributes="Bold" VerticalOptions="Center"/>
<Label Text=""
VerticalOptions="EndAndExpand">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={x:Reference homePage}, Path=BindingContext.DeleteCommand}"
CommandParameter="{Binding}"/>
</Label.GestureRecognizers>
</Label>
</StackLayout>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<Button Style="{StaticResource CircularButtonStyle}"
Command="{Binding AddCommand}"
Text="+"/>
</StackLayout>
</rxui:ReactiveContentPage>
In the HomeViewModel we will use the ItemManager to get the items, a DeleteCommand to remove elements, and an Add Command to Add/Edit an item, this command navigates to the ItemViewModel.
namespace ReactiveToDoSample.ViewModels { public class HomeViewModel : ViewModelBase { public HomeViewModel(IParameterViewStackService navigationService, IItemManager itemManager) : base(navigationService) { DeleteCommand = ReactiveCommand.Create<Item>(itemManager.Remove);
itemManager .ItemChanges .Bind(out _items) .DisposeMany() .Subscribe() .DisposeWith(Subscriptions);
itemManager.AddOrUpdate(new Item(Guid.NewGuid().ToString(), "Family vacation planning")); itemManager.AddOrUpdate(new Item(Guid.NewGuid().ToString(), "Buy Christmas Gifts")); itemManager.AddOrUpdate(new Item(Guid.NewGuid().ToString(), "Go to the Bank")); itemManager.AddOrUpdate(new Item(Guid.NewGuid().ToString(), "Buy Milk"));
AddCommand = ReactiveCommand.CreateFromObservable(() => NavigationService.PushModal<ItemViewModel>());
ViewCommand = ReactiveCommand.CreateFromObservable<Item, Unit>((item) => { SelectedItem = null; return NavigationService.PushModal<ItemViewModel>(new NavigationParameter() { { NavigationParameterConstants.ItemId , item.Id } }); });
this.WhenAnyValue(x => x.SelectedItem) .Where(x => x != null) .InvokeCommand(ViewCommand) .DisposeWith(Subscriptions);
}
public ReactiveCommand<Unit, Unit> AddCommand { get; }
public ReactiveCommand<Item, Unit> ViewCommand { get; }
public ReactiveCommand<Item, Unit> DeleteCommand { get; }
public Item SelectedItem { get => _selectedItem; set => this.RaiseAndSetIfChanged(ref _selectedItem, value); }
public ReadOnlyObservableCollection<Item> Items => _items;
public override string Id => "Reactive ToDo";
private readonly ReadOnlyObservableCollection<Item> _items; private Item _selectedItem; } }
10. Create the ItemPage/ItemModel
A simple page with an entry and an add button.
<?xml version="1.0" encoding="UTF-8" ?> <rxui:ReactiveContentPage x:Class="ReactiveToDoSample.Views.ItemPage" xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:rxui="clr-namespace:ReactiveUI.XamForms;assembly=ReactiveUI.XamForms" xmlns:vm="clr-namespace:ReactiveToDoSample.ViewModels" x:TypeArguments="vm:ItemViewModel"> <rxui:ReactiveContentPage.ToolbarItems> <ToolbarItem Command="{Binding CloseCommand}" Text="Close" /> </rxui:ReactiveContentPage.ToolbarItems> <StackLayout> <Label Text="Item" FontSize="Title" Padding="20" HorizontalOptions="Center"/> <Frame Style="{StaticResource CardFrameStyle}" Margin="10" VerticalOptions="Start"> <StackLayout HorizontalOptions="FillAndExpand"> <Entry Placeholder="Title" Text="{Binding Title}" /> <Button Command="{Binding SaveCommand}" Style="{StaticResource MainButtonStyle}" Text="Save"/> </StackLayout> </Frame> </StackLayout> </rxui:ReactiveContentPage>
This ViewModel will receive the ItemId passed from the HomeViewModel and the ItemManager will add or update that item. If no ItemId passed will create a new Item.
namespace ReactiveToDoSample.ViewModels { public class ItemViewModel : ViewModelBase { public ItemViewModel(IParameterViewStackService navigationService, IItemManager itemManager) : base(navigationService) { _itemManager = itemManager;
var canExecute = this.WhenAnyValue(x => x.Title, (title) => !string.IsNullOrEmpty(title));
SaveCommand = ReactiveCommand.Create(ExecuteSave, canExecute);
CloseCommand = ReactiveCommand.CreateFromObservable(() => NavigationService.PopModal());
SaveCommand .InvokeCommand(CloseCommand) .DisposeWith(Subscriptions);
this.WhenAnyValue(x => x.ItemId) .Where(x => x != null) .Select(x => _itemManager.Get(x)) .Where(x => x.HasValue) .Select(x => x.Value) .Subscribe(x => { Title = x.Title;
}) .DisposeWith(Subscriptions); }
public override IObservable<Unit> WhenNavigatingTo(INavigationParameter parameter) { if (parameter.TryGetValue(NavigationParameterConstants.ItemId, out string itemId)) { ItemId = itemId; }
return base.WhenNavigatedTo(parameter); }
private void ExecuteSave() => _itemManager.AddOrUpdate(new Item(ItemId ?? Guid.NewGuid().ToString(), Title));
public ReactiveCommand<Unit, Unit> SaveCommand { get; }
public ReactiveCommand<Unit, Unit> CloseCommand { get; }
public override string Id => string.Empty;
public string Title { get => _title; set => this.RaiseAndSetIfChanged(ref _title, value); }
private string ItemId { get => _itemId; set => this.RaiseAndSetIfChanged(ref _itemId, value); }
private string _title; private string _description; private readonly IItemManager _itemManager; private string _itemId; } }
Result
You can find the full source code of the sample here.
Happy Reactive ToDo!
Recommend
-
4
Using Lottie Animations in Xamarin.Forms410 views•Dec 24, 2020530ShareSave
-
8
I have recently been exploring DynamicData and since I started using it, I can say that it has been a game changer in my applications. It is quite easy to use and provides a lot of flexibility when working with collections. So, I decided to w...
-
11
react-fluid-form Reactive forms for react and react native, using hooks and Mobx@6. Installation: npm install -s react-fluid-form mobx mobx-react yup lodash // or: yarn add react-fluid-form mobx mobx-reac...
-
10
Using OIDC client in Xamarin Forms to refresh your access token May 25, 2021
-
6
Handling User Interaction Using Xamarin Forms (XF) December 13, 2018 2 Comments
-
11
Performance, XamarinImproving performance App using compiled bindings in Xamarin Forms
-
5
In the first part of this series about State Machine, we covered the basics about it: how to set up and how to use the main features. In this part, w...
-
13
-
7
TODO list API written with Golang, Gin and Gorm To run this API you will need: Docker. Once this is installed use the command: docker compose up -d to start up the API...
-
4
Using Strictly Typed Reactive Forms in AngularI continue to play with the new features of Angular, and one pending task is to learn about Typed Reactive Forms. The strict forms help us avoid many common issu...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK