Search code examples
androidandroid-filterable

Filterable with multi-select list


I would like to have a multiselect spinner with a search option for it. I have already implemented a spinner with a search option. I would like some help with multiselect.

Here is my code:

Adapter class:

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 ViewModels;

namespace Adapters
{
    public class FilterableAdapter : ArrayAdapter, IFilterable
    {
        LayoutInflater inflater;
        Filter filter;
        Activity context;
        public List<FilterableListViewModel> AllItems;
        public List<FilterableListViewModel> MatchItems;

        public FilterableAdapter(Activity context, int txtViewResourceId, List<FilterableListViewModel> items) : base(context, txtViewResourceId, items)
        {
            inflater = context.LayoutInflater;
            filter = new SuggestionsFilter(this);
            AllItems = items;
            MatchItems = items;
        }

        public override int Count
        {
            get
            {
                return MatchItems.Count;
            }
        }

        public override Java.Lang.Object GetItem(int position)
        {
            return null;
        }

        public FilterableListViewModel GetMatchedItem(int position)
        {
            return MatchItems[position];
        }


        public override View GetView(int position, View convertView, ViewGroup parent)
        {
            View view = convertView;
            if (view == null)
                view = inflater.Inflate(Resource.Drawable.filterList_view, null);

            view.FindViewById<TextView>(Resource.Id.list_itemContent).Text = MatchItems[position].displayName;
            //if (!string.IsNullOrEmpty(MatchItems[position].subItem))
            //{
            //    view.FindViewById<TextView>(Resource.Id.list_subItemContent).Text = MatchItems[position].subItem;
            //}
            //else
            //{

            //    view.FindViewById<TextView>(Resource.Id.list_subItemContent).Visibility = ViewStates.Gone;
            //}
            return view;
        }

        public override Filter Filter
        {
            get
            {
                return filter;
            }
        }

        public void ResetSearch()
        {
            MatchItems = AllItems;
            NotifyDataSetChanged();
        }

        class SuggestionsFilter : Filter
        {
            readonly FilterableAdapter _adapter;

            public SuggestionsFilter(FilterableAdapter adapter) : base()
            {
                _adapter = adapter;
            }

            protected override Filter.FilterResults PerformFiltering(Java.Lang.ICharSequence constraint)
            {
                FilterResults results = new FilterResults();
                if (!String.IsNullOrEmpty(constraint.ToString()))
                {
                    var searchFor = constraint.ToString();
                    Console.WriteLine("searchFor:" + searchFor);
                    var matchList = new List<FilterableListViewModel>();
                    //var matches = _adapter.AllItems.Where(i => i.title.ToLower().Contains(searchFor.ToLower()) || string.IsNullOrEmpty(i.subText) ? 1 == 1 : i.subText.ToLower().Contains(searchFor.ToLower()));

                    var matches = _adapter.AllItems.Where(i => i.item.ToLower().Contains(searchFor.ToLower()) || (i.subItem != null && i.subItem.ToLower().Contains(searchFor.ToLower())));//   !string.IsNullOrEmpty(i.subText) ? i.subText.ToLower().Contains(searchFor.ToLower()) : null);


                    foreach (var match in matches)
                    {
                        matchList.Add(match);
                    }

                    _adapter.MatchItems = matchList;
                    Console.WriteLine("resultCount:" + matchList.Count);

                    List<FilterableListViewModel> matchObjects = new List<FilterableListViewModel>();
                    for (int i = 0; i < matchList.Count; i++)
                    {
                        matchObjects.Add(matchList[i]);
                    }

                    results.Count = matchList.Count;
                }
                else
                {
                    _adapter.ResetSearch();
                }
                return results;
            }

            protected override void PublishResults(Java.Lang.ICharSequence constraint, Filter.FilterResults results)
            {
                _adapter.NotifyDataSetChanged();
            }
        }
    }
}

FilterableListViewModel.cs:

public class FilterableListViewModel
{
   public string id { get; set; }
   public string item { get; set; }
   public string subItem { get; set; }
   public string displayName { get; set; }
}

filterList_view.axml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    style="@style/ListView_style">
  <TextView
      android:id="@+id/list_itemContent"
      android:textColor="#000000"
      android:text="sample"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
       android:textSize="18sp"
      android:layout_marginRight="@dimen/_15sdp"
      android:layout_marginLeft="@dimen/_15sdp"
      android:paddingTop="@dimen/_4sdp" />
</LinearLayout>

Activity:

   [Activity(Label = "SuburbListActivity")]
    public class SuburbListActivity : Activity
    {
        private FilterableAdapter _adapter;
        private ListView _list;
        private EditText _filterText;
        private ISuburbs suburbInfo;
        Android.App.ProgressDialog progress;
        public ILog Log { get; private set; }

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            SetContentView(Resource.Layout.filter_list);
            ActionBar.SetHomeButtonEnabled(true);
            ActionBar.SetDisplayHomeAsUpEnabled(true);
            ActionBar.SetDisplayOptions(ActionBarDisplayOptions.ShowCustom, ActionBarDisplayOptions.ShowCustom);
            // ActionBar.SetBackgroundDrawable(new ColorDrawable(Android.Graphics.Color.Black));

            LayoutInflater inflater = (LayoutInflater)GetSystemService(Context.LayoutInflaterService);
            View v = inflater.Inflate(Resource.Layout.layout_actionbar_centerTitle, null);

            ActionBar.LayoutParams p = new ActionBar.LayoutParams(
                    ViewGroup.LayoutParams.MatchParent,
                    ViewGroup.LayoutParams.MatchParent,
                   GravityFlags.Center);

            var title = ((TextView)v.FindViewById(Resource.Id.title));
            title.Text = "Select Suburb";

            ActionBar.SetCustomView(v, p);
            ActionBar.SetDisplayShowTitleEnabled(true);
            ActionBar.SetDisplayHomeAsUpEnabled(true);


            progress= new Android.App.ProgressDialog(this);
            progress.SetProgressStyle(Android.App.ProgressDialogStyle.Spinner);
            progress.SetMessage(WaitMessage);
            progress.SetCancelable(false);

            progress.Show();
            InitializeComponant();
            InitializeEvents();
            BindData();

        }

        private void InitializeComponant()
        {
            suburbInfo = Dependencies.Container.Get<ISuburbs>();
            Log = Dependencies.Container.Get<ILog>();

            _list = FindViewById<ListView>(Resource.Id.filterList);
            _filterText = FindViewById<EditText>(Resource.Id.search);

        }

        /// <summary>
        /// initialize evnts
        /// </summary>
        private void InitializeEvents()
        {
            _list.ItemClick += List_ItemClick;
        }

        private void List_ItemClick(object sender, AdapterView.ItemClickEventArgs e)
        {
            var listItem = _adapter.GetMatchedItem(e.Position);
            //var serviceRoleId = listItem.id;
            //var hasPIC = listItem.subItem;
            Intent myIntent = new Intent(this, typeof(RegisterActivity));
            myIntent.PutExtra("suburbId", listItem.id);
            myIntent.PutExtra("suburbName", listItem.item);
            myIntent.PutExtra("postcode", listItem.subItem);
            SetResult(Result.Ok, myIntent);
            Finish();
        }

        private async Task BindData()
        {

            //var serviceRoleList = await serviceInfo.GetAllAsync();
            RegistrationFacade regFacade = new RegistrationFacade(Log, null, null, suburbInfo);
            List<FilterableListViewModel> states = await regFacade.GetSuburbs();

            //List<FilterableListViewModel> testmodel = new List<FilterableListViewModel>();
            //testmodel.Add(new FilterableListViewModel { subItem = "abc, goa, panjim", id = "1", item = "abc" });
            //testmodel.Add(new FilterableListViewModel { subItem = "qwe, maha, vasco", id = "1", item = "qwe" });
            //testmodel.Add(new FilterableListViewModel { subItem = "rty, karna, panjim", id = "1", item = "rty" });
            //testmodel.Add(new FilterableListViewModel { subItem = "uio, assa, marg", id = "1", item = "ttt" });
            _adapter = new FilterableAdapter(this, Resource.Drawable.filterList_view, states);
            _list.Adapter = _adapter;

            _filterText.TextChanged += (object sender, Android.Text.TextChangedEventArgs e) =>
            {
                var searchTerm = _filterText.Text;
                if (String.IsNullOrEmpty(searchTerm))
                {
                    _adapter.ResetSearch();
                }
                else
                {
                    _adapter.Filter.InvokeFilter(searchTerm);
                }
            };

            progress.Hide();

        }
    }

Any help on how I can modify my code to get multiselect list?

This is what I have already:

enter image description here

Here is my adapter after Umair's suggestion:

  public class FilterableMultiselectAdapter : ArrayAdapter, IFilterable
    {
        LayoutInflater inflater;
        Filter filter;
        Activity context;
        public List<FilterableListViewModel> AllItems;
        public List<FilterableListViewModel> MatchItems;

        public FilterableMultiselectAdapter(Activity context, int txtViewResourceId, List<FilterableListViewModel> items) : base(context, txtViewResourceId, items)
        {
            inflater = context.LayoutInflater;
            filter = new SuggestionsFilter(this);
            AllItems = items;
            MatchItems = items;
        }

        public override int Count
        {
            get
            {
                return MatchItems.Count;
            }
        }

        public override Java.Lang.Object GetItem(int position)
        {
            return null;
        }

        public FilterableListViewModel GetMatchedItem(int position)
        {
            return MatchItems[position];
        }


        public override View GetView(int position, View convertView, ViewGroup parent)
        {
            View view = convertView;
            if (view == null)
                view = inflater.Inflate(Resource.Drawable.filterMultiselectList_view, null);

            view.FindViewById<CheckedTextView>(Resource.Id.chkTV).Text = MatchItems[position].displayName;
            return view;
        }

        public override Filter Filter
        {
            get
            {
                return filter;
            }
        }

        public void ResetSearch()
        {
            MatchItems = AllItems;
            NotifyDataSetChanged();
        }

        class SuggestionsFilter : Filter
        {
            readonly FilterableMultiselectAdapter _adapter;

            public SuggestionsFilter(FilterableMultiselectAdapter adapter) : base()
            {
                _adapter = adapter;
            }

            protected override Filter.FilterResults PerformFiltering(Java.Lang.ICharSequence constraint)
            {
                FilterResults results = new FilterResults();
                if (!String.IsNullOrEmpty(constraint.ToString()))
                {
                    var searchFor = constraint.ToString();
                    Console.WriteLine("searchFor:" + searchFor);
                    var matchList = new List<FilterableListViewModel>();
                    //var matches = _adapter.AllItems.Where(i => i.title.ToLower().Contains(searchFor.ToLower()) || string.IsNullOrEmpty(i.subText) ? 1 == 1 : i.subText.ToLower().Contains(searchFor.ToLower()));

                    var matches = _adapter.AllItems.Where(i => i.item.ToLower().Contains(searchFor.ToLower()) || (i.subItem != null && i.subItem.ToLower().Contains(searchFor.ToLower())));//   !string.IsNullOrEmpty(i.subText) ? i.subText.ToLower().Contains(searchFor.ToLower()) : null);


                    foreach (var match in matches)
                    {
                        matchList.Add(match);
                    }

                    _adapter.MatchItems = matchList;
                    Console.WriteLine("resultCount:" + matchList.Count);

                    List<FilterableListViewModel> matchObjects = new List<FilterableListViewModel>();
                    for (int i = 0; i < matchList.Count; i++)
                    {
                        matchObjects.Add(matchList[i]);
                    }

                    results.Count = matchList.Count;
                }
                else
                {
                    _adapter.ResetSearch();
                }
                return results;
            }

            protected override void PublishResults(Java.Lang.ICharSequence constraint, Filter.FilterResults results)
            {
                _adapter.NotifyDataSetChanged();
            }
        }
    }

Solution

  • You can use CheckedTextView for selecting multiple items from the spinner dropdown.

    <?xml version="1.0" encoding="utf-8"?>
    
    <RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    
    <CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/chkTV"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:drawableLeft="?android:attr/listChoiceIndicatorMultiple"
        android:drawableStart="?android:attr/listChoiceIndicatorMultiple"
        android:clickable="true"
        android:focusable="true"
        android:gravity="center_vertical"
        android:padding="5dp"
        android:text="@string/action_settings"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:textStyle="bold" />
    
     </RelativeLayout>
    

    and then in your adapter class's getView method do something like this:

    viewHolder.chkTV.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (viewHolder.chkTV.isChecked()) {
                    viewHolder.chkTV.setChecked(false);
                    if (mModel != null) {
                        addItemIntoUnCheckedList(mModel.getId());
                    }
    
                } else {
                    viewHolder.chkTV.setChecked(true);
                    if (mModel != null) {
                        addItemIntoCheckedList(mModel.getId());
                    }
                }
            }
        });
    
    
     private void addItemIntoCheckedList(int id) {
    
    
        Model phyFac = new Model();
        phyFac.Id = ID;
        // do your model work here for the checkedItem
    
        if (UnCheckedByUser != null && UnCheckedByUser.size() > 0) {
    
    
            for (int i = 0; i < UnCheckedByUser.size(); i++) {
                if (UnCheckedByUser.get(i).getId() == phyFac.getId()) {
                    UnCheckedByUser.remove(i);
                }
            }
        }
        checkedListOfFacilities.add(phyFac);
    
    }
    
    private void addItemIntoUnCheckedList(int Id) {
    
        Model unCheckedPhyFac = new Model();
        unCheckedPhyFac.Id = ID;
        unCheckedPhyFac.Id = Id;
    
        if (checkedList != null && checkedList.size() > 0) {
    
            for (int i = 0; i < checkedList.size(); i++) {
                if (checkedList.get(i).getId() == unCheckedPhyFac.getId()) {
                    checkedList.remove(i);
                }
            }
        }
    
        UnCheckedByUser.add(unCheckedPhyFac);
    }
    

    Now at the end you will get both the unchecked and checked lists using this approach and you can do whatever you want to do with those lists.