Paramjit
Paramjit

Reputation: 860

The ReactiveUI ver 9.19.5 Binding is not working with Xamarin.Forms ver 4.1.0

I am trying to get my head around with ReactiveUI. I created a new project with the following main packages:

  1. Xamarin.Forms 4.1.0.618606
  2. PropertyChanged.Fody 3.0.1
  3. ReactiveUI 9.19.5
  4. ReactiveUI.XamForms 9.19.5

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

Answers (1)

Glenn Watson
Glenn Watson

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

Related Questions