Binding ReactiveList<T> to ListView in Xamarin Forms
Asked Answered
L

2

6

I feel like I am doing this the right way, but am unsure. I am loading objects into a ReactiveList in my ViewModel asynchronously. Through Xamarin Forms, I have bound the List to the ItemSource property of a ListView in Xamarin Forms. I also have a search box which will clear the ReactiveList and add new items to the ReactiveList.

The first time I open the View, no interaction makes the list load, and a button that is bound to a ReactiveCommand for loading more items into the ReactiveList is disabled.

The second time I open the View the ListView renders the items in the new ViewModel almost immediately, but again, interactions do not seem to work. However, changing the search box does actually clear items from the ListView, but items do not get added.

This behavior is on iOS. I am ReactiveUI 6.3.1 and Xamarin Forms 1.3.2.6316.

Here is the simplified problem:

public class MyViewModel : ReactiveObject, IRoutableViewModel
{
    public MyViewModel(IScreen hostScreen = null)
    {
        HostScreen = hostScreen ?? Locator.Current.GetService<IScreen>();

        List = new ReactiveList<ItemViewModel>()
        {
            ChangeTrackingEnabled = true
        };

        //This never gets shown
        List.Add(new ItemViewModel()
        {
            Name = "TEST"
        });

        //Tried doing this on the main thread: same behavior
        LoadItemsCommand = ReactiveCommand.CreateAsyncTask(_ => GetItems()), RxApp.TaskpoolScheduler);

        LoadItemsCommand
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(results =>
            {
                //debugger breaks here in EVERY case and successfully changes List but doesn't necessarily affect the view

                try
                {
                    List.AddRange(results.Select(e => new ItemViewModel()
                    {
                        Name = e.Name
                    }));
                }
                catch (Exception ex)
                {
                    //breakpoint does not break here.
                    Debug.WriteLine(ex.Message);
                    throw;
                }

            });

        //No exceptions here either
        LoadItemsCommand.ThrownExceptions
            .Select(ex => new UserError("Error", "Please check your Internet connection"))
            .Subscribe(Observer.Create<UserError>(x => UserError.Throw(x)));

        this.WhenAnyValue(e => e.SearchText).Subscribe(e => ResetPage());
    }

    private Task<IEnumerable<Item>> GetItems()
    {
        //asynchronously get items
        return ...;
    }

    private int ResetPage()
    {
        List.Clear();
        return 0;
    }

    [DataMember]
    public ReactiveList<ItemViewModel> List { get; private set; }

    private string _searchText;
    [DataMember]
    public string SearchText
    {
        get { return _searchText; }
        set { this.RaiseAndSetIfChanged(ref _searchText, value); }
    }

    public ReactiveCommand<IEnumerable<Item>> LoadItems { get; protected set; }

    public class ItemViewModel : ReactiveObject
    {
        public string Name { get; set; }
    }

    public string UrlPathSegment
    {
        get { return "Page"; }
    }

    public IScreen HostScreen { get; protected set; }


}

My xaml:

  <StackLayout VerticalOptions="FillAndExpand" Orientation="Vertical">
    <Entry x:Name="_searchEntry" Placeholder="Search"></Entry>
    <ListView x:Name="_myListView" RowHeight="80">
      <ListView.ItemTemplate>
        <DataTemplate>
          <ViewCell>
            <StackLayout Orientation="Vertical" >
              <Label Text="{Binding Name}"></Label>
              <Label Text="{Binding Address}"></Label>
              <Label Text="{Binding PhoneNumber}"></Label>
            </StackLayout>
          </ViewCell>
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
    <Button x:Name="_loadMoreButton" Text="Load More" TextColor="White" BackgroundColor="#77D065"></Button>  </StackLayout>
</XamForms:ReactiveContentPage>

My view code-behind:

using System;
using System.Reactive.Concurrency;
using System.Threading.Tasks;

using ReactiveUI;
using ReactiveUI.XamForms;

namespace views
{
    public partial class MyView : ReactiveContentPage<MyViewModel>
    {
        private IDisposable _disconnectHandler;

        public NearbyPlacesView()
        {
            InitializeComponent();

            this.Bind(this.ViewModel, model => model.SearchText, view => view._searchEntry.Text);
            this.OneWayBind(this.ViewModel, model => model.List, view => view._myListView.ItemsSource);
            this.BindCommand(this.ViewModel, model => model.LoadItemsCommand, view => view._loadMoreButton);
        }

        protected override void OnAppearing()
        {
            base.OnAppearing();

            //Is this the proper way to do this? seems
            _disconnectHandler = UserError.RegisterHandler(async error =>
            {
                RxApp.MainThreadScheduler.ScheduleAsync(async (scheduler, token) =>
                {
                    await DisplayAlert("Error", error.ErrorMessage, "OK");
                });

                return RecoveryOptionResult.CancelOperation;
            });

            //Load the items when the view appears. This doesn't feel right though.
            ViewModel.LoadItemsCommand.Execute(null);
        }

        protected override void OnDisappearing()
        {
            base.OnDisappearing();

            _disconnectHandler.Dispose();
            _disconnectHandler = null;
        }
    }
}
Lovejoy answered 7/2, 2015 at 15:11 Comment(2)
I got the initial test entry to be displayed. The reason was because ResetPage() was clearing that initial test entry.Lovejoy
I figured a lot of this out. Placement of calls in the ViewModel constructor are important! I moved the this.WhenAnyValue(...) for the SearchText to before the LoadItemsCommand setup. ResetPage() now calls LoadItemsCommand.Execute(null); And I took out the LoadItemsCommand.Execute(null); from OnAppearing() in the view code behind. However I am still getting the issue where the first time I view the page, nothing appears.Lovejoy
L
5

This seems to be a bug with either Xamarin Forms or ReactiveUI. The issue is documented here: https://github.com/reactiveui/ReactiveUI/issues/806

I changed the type of public ReactiveList<ItemViewModel> List to public ObservableCollection<ItemViewModel> List which fixed the issue.

Lovejoy answered 10/3, 2015 at 0:40 Comment(1)
I'm very surprised at this issue, ReactiveUI impressed me by its stability in the last 2 - 3 years.Standpipe
S
0

Your ItemViewModel is a ReactiveObject, however, you have not coded the setter properly. Remember, you should use this.RaiseAndSetIfChanged in the setter to raise NPC.

Straus answered 10/7, 2015 at 13:0 Comment(2)
Not required for a ReactiveList<> property unless you new up overwriting the original.Intramural
Not sure why is it not required if obviously this did not work?Straus

© 2022 - 2024 — McMap. All rights reserved.