How to use the MvvmCross fluent API to bind a RecyclerView item's TextView to a property of its ViewModel on Android?
Asked Answered
D

2

8

I am using MvvmCross in my Xamarin Android project. I have an MvxActivity with an MvxRecyclerView, that I have assigned an item template in its layout file.

<MvxRecyclerView
    android:id="@+id/my_recycler_view"
    local:MvxItemTemplate="@layout/item_recycler_view" />

The ViewModel is quite simple, it consists just of one property that holds the data to display in the RecyclerView:

public class MainViewModel : MvxViewModel
{
    private IEnumerable<ViewModelItem> _viewModelItems;
    public IEnumerable<ViewModelItem> ViewModelItems
    {
        get { return _viewModelItems; }
        set { SetProperty(ref _viewModelItems, value); }
    }    
}

Generally I like to use the MvvmCross fluent API as much as possible because of the implicit refactoring support. So in my activity, I am binding a property of the MvxRecyclerView like this:

var recyclerView = View.FindViewById<MvxRecyclerView>(Resource.Id.my_recycler_view);
var set = this.CreateBindingSet<MainView, MainViewModel>();
set.Bind(recyclerView)
    .For(v => v.ItemsSource)
    .To(vm => vm.ViewModelItems);
set.Apply();

So far so good. Now, the layout file for the item template basically just contains a TextView:

<LinearLayout>
    <TextView
        android:id="@+id/innerText" />
</LinearLayout>

And my ViewModelItem class looks like this:

public class ViewModelItem
{
    public string Title { get; set; }
}

My question now is, how and where do I bind the TextView.Text property to the ViewModelItem.Title property using the fluent API?

I know it is quite easy to do without the fluent API by supplying an MvxBind attribute in the item template layout file, but I would really prefer a fluent API solution.

Devereux answered 10/10, 2016 at 15:39 Comment(0)
B
17

Inherit from MvxRecyclerAdapter and create a custom Adapter for your RecyclerView. Override OnCreateViewHolder and return a custom ViewHolder.

public class MyAdapter : MvxRecyclerAdapter
{
    public MyAdapter(IMvxAndroidBindingContext bindingContext)
        : base(bindingContext)
    {
    }

    public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
    {
        var itemBindingContext = new MvxAndroidBindingContext(parent.Context, this.BindingContext.LayoutInflaterHolder);
        var view = this.InflateViewForHolder(parent, viewType, itemBindingContext);

        return new MyViewHolder(view, itemBindingContext);
    }
}

Within this ViewHolder you can use the Fluent API for binding.

public class MyViewHolder : MvxRecyclerViewHolder
{
    private readonly TextView textView;

    public MyViewHolder(View itemView, IMvxAndroidBindingContext context)
        : base(itemView, context)
    {
        this.textView = itemView.FindViewById<TextView>(Android.Resource.Id.Text1);

        this.DelayBind(() =>
        {
            var set = this.CreateBindingSet<MyViewHolder, ViewModelItem>();
            set.Bind(this.textView).To(x => x.Title);
            set.Apply();
        });
    }
}

In your Activity create the Adapter and add it to your RecyclerView:

var adapter = new MyAdapter((IMvxAndroidBindingContext)this.BindingContext);
recyclerView.Adapter = adapter;

and bind your Items to the ItemsSource of your Adapter:

set.Bind(this.adapter).For(x => x.ItemsSource).To(x => x.ViewModelItems);
Bedraggled answered 11/10, 2016 at 8:59 Comment(2)
One thing to note, you may want to assign the click command over to your custom ViewHolder else any binding to ItemClick will have no affect. Example in this stackoverflow answer.Harte
This helps a lot, but I need to implement multiple viewtype, how can we implement that with customview holder ? I tried to find but everywhere shows with ItemTemplate Selector which use defaultViewHolder and binding from xml, I want to implement with above pattern.Jetsam
D
2

Based on Ken's answer, I created a couple of support classes and extensions to generalize binding of items and pushed them together with a usage sample to github:

https://github.com/lauxjpn/MvxItemBinder

It allows you to write item bindings like the following:

var recyclerView = FindViewById<MvxRecyclerView>(Resource.Id.RecyclerView);

var set = this.CreateBindingSet<MainActivity, MainViewModel>();
set.Bind(recyclerView)
    .For(v => v.ItemsSource)
    .To(vm => vm.Items);
set.Apply();

recyclerView.BindItems<ItemViewModel>(this, (itemView, itemSet) =>
    itemSet.Bind(itemView.FindViewById<TextView>(Resource.Id.item_template))
        .For(v => v.Text)
        .To(vm => vm.Title)
);

Or even shorter:

var recyclerView = FindViewById<MvxRecyclerView>(Resource.Id.RecyclerView);

var set = this.CreateBindingSet<MainActivity, MainViewModel>();
set.Bind(recyclerView.BindItems<ItemViewModel>(this, (itemView, itemSet) =>
        itemSet.Bind(itemView.FindViewById<TextView>(Resource.Id.item_template))
            .For(v => v.Text)
            .To(vm => vm.Title)))
    .For(v => v.ItemsSource)
    .To(vm => vm.Items);
set.Apply();
Devereux answered 13/10, 2016 at 11:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.