How to Bind data to a Custom ListView with Xamarin Android with Reactive UI

风流意气都作罢 提交于 2020-01-14 02:16:25

问题


I am using Xamarin Android with Reactive UI and Not using Xamarin Forms. I have a Custom ListView(I have defined it's layout as xaml). I have no idea how to bind this control into a observableCollection in ViewModel using OneWayBind method in the activity class.

I wrote it as,

this.OneWayBind(ViewModel, x => x.OutletListing, x => x.List).DisposeWith(SubscriptionDisposables);

But gives,

System.ArgumentException: Can't convert System.Collections.ObjectModel.ObservableCollection1 to Android.Widget.ListView. To fix this, register a IBindingTypeConverter

I saw in tutorials Xamarin Forms has used ItemSource property for this.

Could anyone please give a solution for this . thanks in advance.

Update I have no idea how to go ahead with the given answer. I want to figure this out more.

here is my ViewModel class.

using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Diagnostics;
    using Android.App;
    using Android.Content;
    using Android.OS;
    using Android.Runtime;
    using Android.Views;
    using Android.Widget;
    using ReactiveUI;
    using System.Collections.ObjectModel;
    using System.Reactive.Linq;
    using Splat;
    using System.Reactive.Disposables;
    using System.Threading.Tasks;

    namespace DistributrIII.Mobile.Droid.ViewModels
    {
        public class StockTakeVM : ReactiveObject
        {
            protected Lazy<CompositeDisposable> ViewModelBindings = new Lazy<CompositeDisposable>(() => new CompositeDisposable());
            public void RegisterObservables()
            {
                StockItemListing = new ReactiveList<StockItemListingResult>();

                this.LoadStockItems = ReactiveCommand.CreateFromTask<FilterParams, List<StockItemListingResult>>(
                    async filter =>
                    {
                        System.Diagnostics.Debug.WriteLine("Load StockItemListing #1");
                        var r = await GetStockItemListing(filter);
                        return r;
                    },
                        Observable.Return(true))
                    .DisposeWith(ViewModelBindings.Value);

                this.LoadStockItems.ThrownExceptions
                    .Subscribe(ex =>
                    {
                        System.Diagnostics.Debug.WriteLine("Load StockItemListing Failed");
                    });

                this.LoadStockItems
                   .ObserveOn(RxApp.MainThreadScheduler)
                   .Subscribe(result =>
                   {

                       StockItemListing.Clear();
                       foreach (var item in result)
                           StockItemListing.Add(item);

                   });

            }

            async Task<List<StockItemListingResult>> GetStockItemListing(FilterParams filter)
            {
                List<StockItemListingResult> items = new List<StockItemListingResult>() {  

                new StockItemListingResult
                {
                    StockItemName = "PORK SAUSAGES (ECONOMY) 500G",
                    StockItemCode = "CODE: J3103386",
                    StockItemAmt = "150"
                },

                new StockItemListingResult
                {
                    StockItemName = "COLLAR BACON  500G",
                    StockItemCode = "CODE: J3155667",
                    StockItemAmt = "152"
                },

                new StockItemListingResult
                {
                    StockItemName = "COLLAR BACON 1KG",
                    StockItemCode = "CODE: J2344545",
                    StockItemAmt = "200"
                },

                new StockItemListingResult
                {
                    StockItemName = "PORK CHIPPOLATAS 1KG",
                    StockItemCode = "CODE: J31038779",
                    StockItemAmt = "378"
                },

                new StockItemListingResult
                {
                    StockItemName = "PORK SAUSAGES (ECONOMY) 500G",
                    StockItemCode = "CODE: J3103386",
                    StockItemAmt = "23"
                },
                new StockItemListingResult
                {
                    StockItemName = "PORK SAUSAGES (ECONOMY) 500G",
                    StockItemCode = "CODE: J3103386",
                    StockItemAmt = "454"
                },

                new StockItemListingResult
                {
                    StockItemName = "COLLAR BACON  500G",
                    StockItemCode = "CODE: J3155667",
                    StockItemAmt = "123"
                },

                new StockItemListingResult
                {
                    StockItemName = "COLLAR BACON 1KG",
                    StockItemCode = "CODE: J2344545",
                    StockItemAmt = "675"
                },

                new StockItemListingResult
                {
                    StockItemName = "PORK CHIPPOLATAS 1KG",
                    StockItemCode = "CODE: J31038779",
                    StockItemAmt = "11"
                },

                new StockItemListingResult
                {
                    StockItemName = "PORK SAUSAGES (ECONOMY) 500G",
                    StockItemCode = "CODE: J3103386",
                    StockItemAmt = "34"
                }


            };
                return items;
            }


            // Observable Properties
            ReactiveList<StockItemListingResult> _stockItemListing;
            public ReactiveList<StockItemListingResult> StockItemListing
            {
                get { return _stockItemListing; }
                private set { this.RaiseAndSetIfChanged(ref _stockItemListing, value); }
            }

            ReactiveCommand<FilterParams, List<StockItemListingResult>> _loadStockItems;
            public ReactiveCommand<FilterParams, List<StockItemListingResult>> LoadStockItems
            {
                get { return _loadStockItems; }
                private set { this.RaiseAndSetIfChanged(ref _loadStockItems, value); }
            }
            public StockTakeVM()
            {
                RegisterObservables();
            }

        }

        public class StockItemListingResult : ReactiveObject
        {
            Guid _stockItemId;
            public Guid Id
            {
                get { return _stockItemId; }
                set { this.RaiseAndSetIfChanged(ref _stockItemId, value); }
            }

            string _stockItemName;
            public string StockItemName
            {
                get { return _stockItemName; }
                set { this.RaiseAndSetIfChanged(ref _stockItemName, value); }
            }

            string _stockItemCode;
            public string StockItemCode
            {
                get { return _stockItemCode; }
                set { this.RaiseAndSetIfChanged(ref _stockItemCode, value); }
            }
            string _stockItemAmt;
            public string StockItemAmt
            {
                get { return _stockItemAmt; }
                set { this.RaiseAndSetIfChanged(ref _stockItemAmt, value); }
            }

        }


    }

And My Activity Class is as follows

   using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using ReactiveUI;
using Android.Support.V4.Widget;
using Android.Support.Design.Widget;
using DistributrIII.Mobile.Droid.ViewModels;
using DistributrIII.Mobile.Droid.Util;
using System.Reactive.Disposables;
using DistributrIII.Mobile.Droid.Models;
using Android.Support.V7.Widget;
using Android.Support.V7.App;
using Splat;
using Android.Support.V4.View;
using System.Reactive.Linq;

namespace DistributrIII.Mobile.Droid.Views.StockTake
{
    [Activity(Label = "StockTakeActivity", MainLauncher = true, Theme = "@style/MainTheme")]
    public class StockTakeActivity : DistributrBaseActivity<StockTakeVM>
    {
        private Android.Support.V7.Widget.SearchView _searchView;
        public ListView List { get; private set; }
        ReactiveList<StockItemListingResult> StockListItems;
        StockTakeScreenAdapter osadapter;
        List<StockItemModel> items = new List<StockItemModel>();

        public StockTakeActivity()
        {
            this.ViewModel = new StockTakeVM();
            this.StockListItems = new ReactiveList<StockItemListingResult>();
        }

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            SetContentView(Resource.Layout.Activity_StockTake);


            this.Bind(ViewModel, x => x.StockItemListing, x => x.StockListItems);

            //checking whether change happens
            //        StockListItems.ItemChanged
            //        .Where(x => x.PropertyName == "StockItemAmt" && x.Sender.StockItemAmt)
            //        .Select(x => x.Sender)
            //        .Subscribe(x => {
            //            Console.WriteLine("Make sure to save {0}!", x.DocumentName);
            //});
            this.WhenAnyValue(view => view.ViewModel.StockItemListing)
            .Where(listing => listing != null)
            .Select(listing => new DistributrIII.Mobile.Droid.Util.ReactiveListAdapter<StockTakeVM>(listing, Resource.Layout.Activity_StockTake))
            .BindTo(this, view => view.List.Adapter);

            this.ViewModel.LoadStockItems.Execute(new FilterParams
            {
                NameFilter = "",

                Status = "All"
            }).Subscribe();

            //List = FindViewById<ListView>(Resource.Id.List);

            SetupReactiveLists(this);
            var toolbarST = FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbarST);
            toolbarST.InflateMenu(Resource.Menu.StockTakeSearch);
            toolbarST.Title = "Stock Take";




        }
        public void SetupReactiveLists(Activity context)
        {
            List = FindViewById<ListView>(Resource.Id.List);

            foreach (var item in StockListItems)
            {
                StockItemModel stockitem = new StockItemModel
                {
                    StockItemName = item.StockItemName,
                    StockItemCode = item.StockItemCode
                };

                items.Add(stockitem);
            }
            osadapter = new StockTakeScreenAdapter(this, items);
            List.Adapter = osadapter;

            List.ItemClick += OnListItemClick;

        }




}

here is my Listadapter class.

public class StockTakeScreenAdapter : BaseAdapter<StockItemModel>
    {
        List<StockItemModel> items;
        Activity context;
        public StockTakeScreenAdapter(Activity context, List<StockItemModel> items) : base()
        {
            this.context = context;
            this.items = items;
        }
        public override long GetItemId(int position)
        {
            return position;
        }
        public override StockItemModel this[int position]
        {
            get { return items[position]; }
        }
        public override int Count
        {
            get { return items.Count; }
        }
        public override View GetView(int position, View convertView, ViewGroup parent)
        {
            var item = items[position];
            View view = convertView;
            if (view == null) 
                view = context.LayoutInflater.Inflate(Resource.Layout.ViewCell_StockTake, null);
            view.FindViewById<TextView>(Resource.Id.stockitem_name).Text = item.StockItemName;
            view.FindViewById<TextView>(Resource.Id.stockitem_code).Text = item.StockItemCode;
            view.FindViewById<TextView>(Resource.Id.stockitem_amt).Text = item.StockItemAmt;
            return view;

} }

Here are my questions

  1. Should I want to create another reactive List in the Activity Class in order to bind.
  2. If I changed the value of a list item , does it update the list (I expect to update the value of a list item using another fragment class.)

I am new to android development with RX. Pardon me if I mess up the things. Thanks again.


回答1:


You would use an adapter to accomplish this on Android, just like you would without Reactive Extensions.

In case you want to use a ListView with RxUI, you'd use a ReactiveListAdapter. Unfortunately this isn't really documented yet, so you might want to take a look at the source code: https://github.com/reactiveui/ReactiveUI/blob/develop/src/ReactiveUI/Platforms/android/ReactiveListAdapter.cs

The gist is you create an instance with the ReactiveList supplying the data. The adapter then watches for changes in this list to know when it needs to be updated.

Example:

this.WhenAnyValue (view => view.ViewModel.OutletListing)
    .Where (listing => listing != null)
    .Select (listing => new ReactiveListAdapter (listing, MyViewCreator))
    .BindTo (this, view => view.List.Adapter);

MyViewCreator is a delegate which accepts the initial ViewModel from your list and the parent ViewGroup so you can inflate the row with initial data and return the resulting View.



来源:https://stackoverflow.com/questions/47525544/how-to-bind-data-to-a-custom-listview-with-xamarin-android-with-reactive-ui

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!