Reputation: 860
I am trying to get my head around with ReactiveUI
. I created a new project with the following main packages:
The project have a page
with associated viewmodel
. I want to Bind View with the ViewModel using ReactiveUI
. But the binding does not work. The project build and run but does not fire any property changed notification
neither any command
. The app needs to display a list
of company names
. But the list does not display the collection defined in ViewModel
. The app should allows the user the sort the list with CollectionChangeCommand
with every change in search query
.
The View Code :
<rxui:ReactiveContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:rxui="clr-namespace:ReactiveUI.XamForms;assembly=ReactiveUI.XamForms"
xmlns:local="clr-namespace:Demo"
x:TypeArguments="local:MainViewModel"
x:Class="Demo.MainPage">
<Shell.TitleView>
<SearchBar x:Name="SearchHandler"
Placeholder="SelectCompany" />
</Shell.TitleView>
<StackLayout>
<ListView x:Name="CompaniesListView"
ItemsSource="{Binding Companies}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<StackLayout Orientation="Horizontal"
Margin="10">
<Label x:Name="NameLabel"
Text="{Binding Name}" />
</StackLayout>
<BoxView HorizontalOptions="FillAndExpand"
HeightRequest="1"
Color="BlueViolet" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Text="ClickMe"
x:Name="OnlyButton" />
</StackLayout>
</rxui:ReactiveContentPage>
The code behind:
public partial class MainPage : ReactiveContentPage<MainViewModel>
{
MainViewModel vm;
public MainPage()
{
InitializeComponent();
this.Bind(ViewModel, vm => vm.Query, v => v.SearchHandler.Text);
this.BindCommand(ViewModel, vm => vm.ButtonClickedCommand, v => v.OnlyButton);
}
}
The ViewModel Code:
[AddINotifyPropertyChangedInterface]
public class MainViewModel : ReactiveObject
{
public ObservableCollection<Company> Companies { get; private set; }
public ReactiveCommand<Unit, Unit> ButtonClickedCommand { get; set; }
public ReactiveCommand<Unit, Unit> CollectionChange { get; set; }
public string Query { get; set; }
public MainViewModel()
{
Companies = new ObservableCollection<Company>
{
new Company{Name="EPF CORPORATION"},
new Company{Name="ABC COMPANY PVT. LTD."},
new Company{Name="UNIQUE COMPUTER SYSTEMS"},
new Company{Name="MICROSOFT PRIVATE LIMITED"},
};
this.WhenAny(x => x.Query, x => !string.IsNullOrWhiteSpace(x.Value));
CollectionChange = ReactiveCommand.CreateFromTask(async () => await SortCollection());
ButtonClickedCommand = ReactiveCommand.CreateFromTask(async () => await ButtonClicked());
async Task SortCollection()
{
ObservableCollection<Company> temp;
temp = new ObservableCollection<Company>(Companies.OrderByDescending(m => m.Name.ToLower().StartsWith(Query.ToLower(), StringComparison.CurrentCulture)));
Companies.Clear();
foreach (Company c in temp)
Companies.Add(c);
}
async Task ButtonClicked()
{
await Shell.Current.DisplayAlert("Button Clicked", "The reactive button command fired finally", "OK");
}
}
I just want to use ReactiveObject
andReactiveCommand
. I will stick with Xamarin.Forms Shell till ReactiveShell
is not available.
I will be grateful to anyone who can can show me how to use ReactiveUI correctly with xamarin.forms.
Upvotes: 0
Views: 910
Reputation: 2888
In your MainPage.xaml.cs you never seem to create the ViewModel?
You are using INotifyPropertyChanged fody on a ReactiveObject, there is a ReactiveUI fody out there. You should not generate mix the INotifyPropertyChange on ReactiveObject's.
You have a vm
property but that isn't created either.
On ReactiveContentControl<TViewModel>
it has a property called ViewModel
which you need to set with something. That can be passed in from another control.
Also in your ViewModel.cs
file you have a this.WhenAny
but don't seem to do anything with the observable that is generated?
Be aware also that a better approach for the sorting might be to use the DynamicData framework which is part of the ReactiveUI family now.
You could do something like
ViewModel.cs:
public class MainViewModel : ReactiveObject
{
private readonly SourceList<Company> _companies;
private readonly ReadOnlyObservableCollection<Company> _sortedCompanies;
//Reactive Commands
public ReactiveCommand<Unit, Unit> ButtonClickedCommand { get; }
public ReadOnlyObservableCollection<Company> Companies => _sortedCompanies;
[Reactive]
public string Query { get; set; }
public MainViewModel()
{
_companies = new SourceList<Company>();
_companies.AddRange(new[]
{
new Company{Name="EPF CORPORATION"},
new Company{Name="ABC COMPANY PVT. LTD."},
new Company{Name="UNIQUE COMPUTER SYSTEMS"},
new Company{Name="MICROSOFT PRIVATE LIMITED"},
});
// Delay to once every 500 milliseconds doing an update.
var refreshObs = this.WhenAnyValue(x => x.Query).Throttle(TimeSpan.FromMilliseconds(500));
_companies.Connect()
.AutoRefreshOnObservable(_ => refreshObs)
.Filter(m => Query == null || m.Name.IndexOf(Query, StringComparison.CurrentCultureIgnoreCase) >= 0) // If it contains a portion of the text.
.Sort(SortExpressionComparer<Company>.Ascending(t => t.Name))
.ObserveOn(RxApp.MainThreadScheduler)
.Bind(out _sortedCompanies)
.Subscribe();
ButtonClickedCommand = ReactiveCommand.CreateFromTask(async () => await ButtonClicked());
}
async Task ButtonClicked()
{
await Shell.Current.DisplayAlert("Button Clicked", "The reactive button command fired finally", "OK");
}
}
MainPage.xaml.cs
public partial class MainPage : ReactiveContentPage<MainViewModel>
{
public MainPage()
{
InitializeComponent();
ViewModel = new MainViewModel();
//ReactiveUI Bindings
this.Bind(ViewModel, vm => vm.Query, v => v.SearchHandler.Text);
this.BindCommand(ViewModel, vm => vm.ButtonClickedCommand, v => v.OnlyButton);
this.OneWayBind(ViewModel, vm => vm.Companies, view => view.CompaniesListView.ItemsSource);
}
}
I added the ReactiveUI.Fody package included ReactiveUI
in the FodyWeavers.xml
Also I changed your ListView to not do the XAML binding so <ListView x:Name="CompaniesListView">
This seems to be working well for me.
Upvotes: 1