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

Using Spinner Control for Filtering ListView - Android

4.86/5 (10 votes)
13 Apr 2015CPOL2 min read 53.8K  
Filter ListView Items using Spinner ( with custom adapters, Model Classes )

Introduction

In this tip, we will look into a common scenario where the Items in the Listview are filtered according to the Spinner (i.e., Dropdown control in Android) selection. We will use Entity or Model Classes for populating our Spinner and ListView controls. Adapter is also needed to populate the data. For the sake of simplicity, array of objects are used to demonstrate the data population. We use City and Address Entities as an example.

Using the Code

Step 1

We start with creating a new sample project and add a blank activity. Select the appropriate API levels as per your choice. Now, we have a default MainActivity class file and a corresponding layout file created. From the next step onwards, we will be only focusing on the vital parts needed to accomplish our task of populating and filtering the listview with spinner.

Step 2

Create both the Model Classes needed, i.e., State and Address

Java
// City 
public class City {
    private long id;
    private String city;    

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }   
}

// Address
public class Address {
    private long id;
    private long cityId;
    private String buildingName;
    private String street;
    private String area;    

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }
    public long getCityId() {
        return cityId;
    }

    public void setCityId(long cityId) {
        this.cityId = cityId;
    }

    public String getBuildingName() {
        return buildingName;
    }

    public void setBuildingName(String buildingName) {
        this.buildingName = buildingName;
    }
    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }
    public String getArea() {
        return area;
    }

    public void setArea(String area) {
        this.area = area;
    }
}

Step 3

Now let's design the Layout which needs Android Spinner and Listview Controls. Here, we use a Relative Layout with some basic padding and styling. And then, we have to design a simple ListItem layout to display our filtered Addresses inside the ListView.

activity_main.xml

XML
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MainActivity"
    android:background="@color/background_floating_material_light">

    <Spinner
        android:id="@+id/citySpinner"
        android:layout_width="fill_parent"
        android:layout_height="50dp"
        android:spinnerMode="dropdown"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="10dp"
        android:dropDownSelector="@android:drawable/btn_dropdown"
android:background="#0fddaf"

        >

    </Spinner>
    <ListView
        android:id="@+id/address_listview"
        android:layout_below="@id/citySpinner"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"
        android:background="#00ff7f"

        />
</RelativeLayout>

address_listitem.xml

XML
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">
    <TextView
        android:id="@+id/nameTV"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentLeft="true"

        android:textSize="18sp"
        android:textStyle="bold"
        android:layout_gravity="center_vertical">
    </TextView>

    <TextView
        android:id="@+id/streetTv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/nameTV"
        android:textSize="13sp"
        android:layout_gravity="center_vertical">
    </TextView>

    <TextView
        android:id="@+id/areaTV"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/streetTv"
        android:textSize="13sp"
        android:layout_gravity="center_vertical">
    </TextView>

</RelativeLayout>

Step 4

Create the Adapters for Data population. CityAdapter takes type of City which means its strongly typed. (It can be altered as per your needs to make it more generic by replacing City by a generic parent type).

Java
public class CityAdapter extends ArrayAdapter<City> {

    private Context context;
    private List<City> cityList;

    public CityAdapter(Context context, int textViewResourceId,
                       List<City> values) {
        super(context, textViewResourceId, values);
        this.context = context;
        this.cityList = values;
    }

    public int getCount(){
        return cityList.size();
    }

    public City getItem(int position){
        return cityList.get(position);
    }

    public long getItemId(int position){
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        TextView view = new TextView(context);
        view.setTextColor(Color.BLACK);
        view.setGravity(Gravity.CENTER);
        view.setText(cityList.get(position).getCity());

        return view;
    }

    //View of Spinner on dropdown Popping

    @Override
    public View getDropDownView(int position, View convertView,
                                ViewGroup parent) {
        TextView view = new TextView(context);
        view.setTextColor(Color.BLACK);
        view.setText(cityList.get(position).getCity());
        view.setHeight(60);

        return view;
    }
}

Similarly,

Address Adapter

In this Adapter Class, we have used common ViewHolder pattern for optimizing performance and reusability of View. Also, note that our Adapter implements Filterable class which enables us to filter as per the Spinner Selection. It's just the same behaviour as we do autocomplete search using an EditText in Listview. In our scenario, it will be much useful when using JSON queries to populate and filter which helps in filtering once the Data is fetching from Json API calls.

Filterable Interface requires us to implement the getFilter() method which enables us to call the getFilter() method from our MainAcitivity.java class by returning a Filter type. AddressFilter innerclass serves the internal implementation of actual filtering and returns the filtered items implementing performfiltering() and publishResults() methods.

Java
public class AddressAdapter extends BaseAdapter implements Filterable {

    private LayoutInflater mLayoutInflater;
    private List<Address> addressList;
    private List<Address> addressFilterList;
    private AddressFilter addressFilter;
    private Context context;

    public AddressAdapter(Context context, List data){
        addressList = data;
        addressFilterList=data;
        mLayoutInflater = LayoutInflater.from(context);
        this.context = context;
    }

    @Override
    public int getCount() {
        return addressList.size();
    }

    @Override
    public Address getItem(int position) {
        return addressList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View view, ViewGroup parent) {

        View updateView;
        ViewHolder viewHolder;
        if (view == null) {
            updateView = mLayoutInflater.inflate(R.layout.address_listitem, null);
            viewHolder = new ViewHolder();

            viewHolder.tvName = (TextView) updateView.findViewById(R.id.nameTV);
            viewHolder.tvArea = (TextView) updateView.findViewById(R.id.areaTV);
            viewHolder.tvStreet = (TextView) updateView.findViewById(R.id.streetTv);

            updateView.setTag(viewHolder);

        } else {
            updateView = view;
            viewHolder = (ViewHolder) updateView.getTag();
        }

        final Address item = getItem(position);

            viewHolder.tvName.setText(item.getBuildingName());
            viewHolder.tvArea.setText(item.getArea());
            viewHolder.tvStreet.setText(String.valueOf(item.getStreet()));

        return updateView;
    }

    @Override
    public Filter getFilter() {
        if (addressFilter == null) {
            addressFilter = new AddressFilter();
        }
        return addressFilter;
    }

    static class ViewHolder{
        TextView tvName;
        TextView tvArea;
        TextView tvStreet;
    }

// InnerClass for enabling Search feature by implementing the methods

   private class AddressFilter extends Filter
    {

        @Override
        protected FilterResults performFiltering(CharSequence constraint) {

//below checks the match for the cityId and adds to the filterlist
            long cityId= Long.parseLong(constraint.toString());
            FilterResults results = new FilterResults();

            if (cityId > 0) {
                ArrayList<Address> filterList = new ArrayList<Address>();
                for (int i = 0; i < addressFilterList.size(); i++) {

                    if ( (addressFilterList.get(i).getCityId() )== cityId) {

                        Address address = addressFilterList.get(i);
                        filterList.add(address);
                    }
                }

                results.count = filterList.size();
                results.values = filterList;

            } else {

                results.count = addressFilterList.size();
                results.values = addressFilterList;

            }
            return results;
        }
//Publishes the matches found, i.e., the selected cityids
        @Override
        protected void publishResults(CharSequence constraint,
                                      FilterResults results) {

            addressList = (ArrayList<Address>)results.values;
            notifyDataSetChanged();
        }
    }
}

And finally, our MainActivity.java which contains the Spinner and ListView.

Java
public class MainActivity extends Activity implements AdapterView.OnItemSelectedListener {

    private List<Address> addressEntityList = new ArrayList<Address>();
    private List<City> cityEntityList = new ArrayList<City>();
    private ListView listView;
    private AddressAdapter adapter;
    private CityAdapter cityAdapter;
    private Spinner citySpinner;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        citySpinner = (Spinner)findViewById(R.id.citySpinner);
        listView = (ListView) findViewById(R.id.address_listview);
        cityAdapter = new CityAdapter(this,android.R.layout.simple_spinner_dropdown_item,loadDummyCities());
        cityAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        citySpinner.setAdapter(cityAdapter);
        citySpinner.setOnItemSelectedListener(this);
        loadDummyAddress();
        adapter = new AddressAdapter(this, addressEntityList);
        listView.setAdapter(adapter);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    private List<City> loadDummyCities(){
        cityEntityList = new ArrayList<City>();

        City city1 = new City();
        city1.setId(1);
        city1.setCity("Kochi");
        cityEntityList.add(city1);
        City city2 = new City();
        city2.setId(2);
        city2.setCity("Bangalore");
        cityEntityList.add(city2);
        City city3 = new City();
        city3.setId(3);
        city3.setCity("Delhi");
        cityEntityList.add(city3);

        return cityEntityList;
    }

    private List<Address> loadDummyAddress(){

        addressEntityList = new ArrayList<Address>();
        Address address1 = new Address();
        address1.setId(1);
        address1.setCityId(1);
        address1.setArea("Kalamassery");
        address1.setBuildingName("Kinfra");
        address1.setStreet("2nd");
        addressEntityList.add(address1);

        Address address2 = new Address();
        address2.setId(2);
        address2.setCityId(2);
        address2.setArea("Banaswadi");
        address2.setBuildingName("Sharmi");
        address2.setStreet("2nd Cross");
        addressEntityList.add(address2);

        Address address3 = new Address();
        address3.setId(3);
        address3.setCityId(2);
        address3.setArea("MG Road");
        address3.setBuildingName("Carlton");
        address3.setStreet("Church Street");
        addressEntityList.add(address3);

        Address address4 = new Address();
        address4.setId(4);
        address4.setCityId(1);
        address4.setArea("Thrissur");
        address4.setBuildingName("New");
        address4.setStreet("Vatanappilly");
        addressEntityList.add(address4);

        return addressEntityList;
    }

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        City city = cityAdapter.getItem(position);

//Here we use the Filtering Feature which we implemented in our Adapter class.
        adapter.getFilter().filter(Long.toString(city.getId()),new Filter.FilterListener() {
            @Override
            public void onFilterComplete(int count) {

            }
        });
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {

    }
}

Simple dummy lists are used here for the sake of simplicity. UI needs to be tweaked for proper look and feel as I haven't put much effort for the UI side. You can download the source code which is uploaded with the tip.

Points of Interest

Usually, it's a very common requirement we all face where ListViews are filtered using a Spinner Control. I will appreciate your comments if you have any queries or doubts regarding the post. Happy coding. :)

History

  • 13th April, 2015: Initial publication with source code

License

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