Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Xamarin

Android Recycler View with Spinner Item Change Selection and Update UI

5.00/5 (5 votes)
25 Sep 2015CPOL4 min read 79K  
This article helps you to understand how to listen to nested controls inside the recycler view and update the view dynamically.

Introduction

As I'm keep learning Xamarin cross platform , I was developing sample app which has recycler view and the view has nested controls like spinner and checkboxes. I was finding difficult to dynamically update the view row when I changed with spinner drop down value. So, I came up with a idea to event listner to the spinner drop down and pass the event listner to the main activity. In the main activity I can just update the underlying dataset and then there is option called notifyitemchanged, which will update the view row recycler list, based on the index provided.

Using the code

To start with, this article is about creating a recycler view in Xamarin Android and then adding spinner control on the each row and updating the list row dynamically based on the spinner drop down value selected. The basic concept of how to recycler view is explained on the Xamarin guid page very neatly (https://developer.xamarin.com/guides/android/user_interface/recyclerview/). My explanation here is limited to show how to add nested controls on the recycler view row and listen to the event listener of those nested controls and update the list row.

Let's start with the code; I have created ViewHolder class, which will have reference to the view objects. The view holder class has spinner object and then the spinner object is assigned to the event "ItemSelected" in the ViewHolder class constructor. Here we are registering event handlers (in this case itemlistner and spinnerItemSelected) with the View objects. Note that ItemSelected event is an event called after the user has selected the value from the spinner drop down list and also I have added another event handler (i.e itemlistner) which tell which row is touched in the recycler view. Below is the complete example of the View Holder class.

public class VegeViewHolder: RecyclerView.ViewHolder
{
    public ImageView Image { get; set; }
    public TextView Name { get; set; }
    public Spinner Quantity { get; set; }
    public TextView Price { get; set; }
    public TextView TotalAmount { get; set; }

    // Get references to the views defined in the CardView layout.
    public VegeViewHolder(View itemView, Action <int > itemlistner, Action <object,AdapterView.ItemSelectedEventArgs > spinnerItemSelected )
        :base(itemView)
    {
        Image = itemView.FindViewById <ImageView > (Resource.Id.list_image);
        Name = itemView.FindViewById <TextView > (Resource.Id.Name);
        Price = itemView.FindViewById <TextView > (Resource.Id.Price);
        Quantity = itemView.FindViewById <Spinner > (Resource.Id.spinner1);
        TotalAmount = itemView.FindViewById <TextView > (Resource.Id.total);

        itemView.Click += (sender, e) = > itemlistner (base.Position);
        Quantity.ItemSelected+= new EventHandler <AdapterView.ItemSelectedEventArgs > (spinnerItemSelected);
    }

}

Let's create a custom adapter class for the recycler view, Most of the "heavy-lifting" of our RecyclerView integration code takes place in the adapter. RecyclerView requires that we provide an adapter derived from RecyclerView.Adapter to access our data source and populate each item with content from the data source. In this adapter class I have created a event handler which is assigned to the spinner control.

public event EventHandler <int > ItemClick;
public event EventHandler <AdapterView.ItemSelectedEventArgs > SpinnerItemSelectionChanged;

When implementing the recycler adapter we must override the following methods

  • OnCreateViewHolder – Instantiates the item layout file and view holder.
  • OnBindViewHolder – Loads the data at the specified position into the views whose references are stored in the given view holder.
  • ItemCount – Returns the number of items in the data source.

The layout manager calls these methods while it is positioning items within the RecyclerView. More detail information can be found at Xamarin Recycler view guide. Let's create the OnCreateViewHolder method and Instantiates the view holder class and pass the event listener to the constructor.

 

public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
    // Inflate the CardView for the photo:
    View itemView = LayoutInflater.From(parent.Context).
        Inflate(Resource.Layout.list_items, parent, false);

    // Create a ViewHolder to find and hold these view references, and
    // register OnClick with the view holder:
    VegeViewHolder vh = new VegeViewHolder(itemView, OnClick,spinner_ItemSelected);
    return vh;
}

void OnClick(int position)
{
    if (ItemClick != null)
        ItemClick (this, position);
}

void spinner_ItemSelected (object sender, AdapterView.ItemSelectedEventArgs e)
{
    if (SpinnerItemSelectionChanged != null)
        SpinnerItemSelectionChanged (sender, e);
}

Now, lets create OnBindViewHolder. When the layout manager is ready to display a particular view in the RecyclerView's visible screen area, it calls the adapter's OnBindViewHolder method to fill the item at the specified row position with content from the data source.

public override void OnBindViewHolder(RecyclerView.ViewHolder viewHolder, int position)
{
    var item = Items[position];

    var vh = viewHolder as VegeViewHolder;
    var spinnerPos = 0;
    var adapter =new ArrayAdapter<String>(Context, Android.Resource.Layout.SimpleSpinnerItem, _quantity);
    adapter.SetDropDownViewResource (Android.Resource.Layout.SimpleSpinnerDropDownItem);

    vh.Name.Text = item.Name;
    vh.Price.Text = string.Format("Price: ${0}",item.Price);
    vh.ItemView.Tag = position;
    if (item.Quantity > 0) {
        spinnerPos = adapter.GetPosition (item.Quantity.ToString ());
        vh.TotalAmount.Text = string.Format ("${0}", item.Price * item.Quantity);
    } else {
        vh.TotalAmount.Text = "";
    }
    vh.Quantity.Tag = position; //keep reference to list view row position
    vh.Quantity.Adapter = adapter;
    vh.Quantity.SetSelection (spinnerPos);
    vh.Image.SetImageResource (item.ImageId);
}

In the above code, at very high level, we are assigning values to the view objects at a specific row position with the values from the dataset.

Now its time to assign these event handlers to the main activity class. Therefore whenever user touches or change spinner value, these events are handled at activity class level. we'll create an item-click event handler method and SpinnerItemSelectionChangedEvent in our MainActivity. This handler briefly displays a toast that indicates which item row was touched and when spinner value is changed it toasts about the value selected from spinner drop down:

void OnItemClick(object sender, int position)
{
    // Display a toast that briefly shows the enumeration of the item selected
    int itemPosition = position + 1;
    Toast.MakeText(this, "Vegetable Item " + itemPosition, ToastLength.Short).Show();
}

void SpinnerItemSelectionChangedEvent(object sender, AdapterView.ItemSelectedEventArgs e)
{
    Spinner spinner = (Spinner)sender;
    var itemPosition = Convert.ToInt32 (spinner.Tag);

    var currentquantity = vegeList[itemPosition].Quantity;
    var selectedQuantity = Convert.ToInt32( spinner.GetItemAtPosition (e.Position).ToString());
    if (currentquantity != selectedQuantity) {
        vegeList [itemPosition].Quantity = selectedQuantity;
        mAdapter.NotifyItemChanged (itemPosition);
    }

}

From the above code SpinnerItemSelectionChangedEvent, as you can see we implementing notifyitemchanged when the Quantity(i.e spinner object) value has been changed.

  • NotifyItemChanged – Signals that the item at the specified position has changed.

Once the NotifyItemChanged is called, the recyclerview at the specified position will be updated/redrawn. The trick here is to make sure NotifyItemChanged is called only if the oldvalue is not same as new value. Other wise, the adapter will go infinite loop, because when the item row is redrawn and assigning the event handler to spinner object, the SpinnerItemSelectionChangedEvent is always triggered which then triggers NotifyItemChanged. Therefore have to make sure NotifyItemChanged is called only when the dataset values are different to the current value to update the row.

That's all about coding part, see below the end result.

Final Result

Final images here:

Image 1

 

Image 2

 

 

Image 3

 

Points of Interest

What we have tried to do in this article is understand what is how to handle events of the nested controls placed inside android recycler view. It just not only the spinner control as explained in the above example, we can also implement event handlers to the checkbox, button, edit text controls when it placed on the recycler view.

History

SEP 2015 - Initial document

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)