Android SortedList – Sort Multiple Data Types recyclerView

Introduction to SortedList

Android SDK provides a class called SortedList that provides us an easy way to sort data. SortedList is able to respect the order of items added to it and notify of changes to that order.

It was added in android version 22.1.0 and belongs to the android.support.v7.util package. As a class it’s concrete and only extends the java.lang.Object class:

java.lang.Object
   ↳    android.support.v7.util.SortedList<T>

To order items it uses the compare(Object,Object) method. That method uses binary search to fetch the items to be ordered.

The order of items and change notifications can be controlled via the SortedList.Callback parameter.

SortedList has two inner classes:

No. Class Function
1. SortedList.Callback<T2> A callback implementation that can batch notify events dispatched by the SortedList.
2. SortedList.BatchedCallback<T2> The class that controls the behavior of the SortedList.

Example – RecyclerView Sort based on different types/field using SortedList

Let’s come create an example to allow us sort a recyclerview based on different fields. The app will list stars. Each star will have a name, comments, favorites and views. Users can then sort for based on number of comments, favorites, view count and name. They can do it both in ascending and descending manner of the above properties.

 

Video Tutorial

Watch the video tutorial below for more details.Please remember to like and subscribe.

Gradle Scripts

Start by adding the following dependencies in your app level build.gradle file:

    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.cardview:cardview:1.0.0'
    implementation 'com.google.android.material:material:1.0.0'
    implementation 'androidx.recyclerview:recyclerview:1.0.0'

}

You can see there is no special library or dependency we are using, just common androidx components.

Layouts

We will have three layoust:

No. Layout Role
1. activity_main.xml Is our main activity’s layout.
2. model.xml Will be inflated into our recyclerview items when sorting in ascending manner.
3. model_grid.xml Will be inflated into our recyclerview items when sorting in descending manner.
(a). activity_main.xml

In your activity_main.xml file add the following code:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <LinearLayout
        android:id="@+id/outerLayout"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
        <Button
                android:id="@+id/ascendigBtn"
                android:background="@color/colorAccent"
                android:layout_weight="0.25"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:textColor="@android:color/white"
                android:textStyle="bold"
                android:text="ASC"
            android:drawableEnd="@drawable/ic_keyboard_arrow_up_white_24dp"
            android:drawableRight="@drawable/ic_keyboard_arrow_up_white_24dp" />
        <Button
            android:id="@+id/sortByCommentsBtn"
            android:background="@android:color/holo_red_light"
            android:layout_weight="0.25"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textColor="@android:color/white"
            android:textStyle="bold"
            android:text="DESC"
            android:drawableEnd="@drawable/ic_keyboard_arrow_down_white_24dp"
            android:drawableRight="@drawable/ic_keyboard_arrow_down_white_24dp" />
        <RelativeLayout
            android:background="@android:color/darker_gray"
            android:layout_weight="0.5"
            android:layout_width="0dp"
            android:layout_height="match_parent">
            <Spinner
                android:id="@+id/propertySpinner"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"/>
        </RelativeLayout>

    </LinearLayout>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/myRecyclerView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    </LinearLayout>
    <Button
        android:text="SortedList Tutorial"
        android:background="@color/colorPrimary"
        android:textColor="@android:color/white"
        android:layout_alignParentBottom="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</RelativeLayout>

You can see we have the following components used above:

  1. RecyclerView – To render our data.
  2. Buttons – To sort in ascending/descending manner.
  3. Spinner – To choose the field to sort with e.g comments,views,favorites.
(b). model.xml

To model a single item to be rendered as part of the list of items in our recyclerview. Will be inflated when we are using LinearLayouManager.

<?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"
    android:background="@color/colorAccent"
    android:padding="10dp"
    android:layout_marginTop="10dp"
    android:layout_marginLeft="10dp"
    android:layout_marginRight="10dp"
    android:id="@+id/layoutQuestion_item"
    android:clickable="true">

    <TextView
        android:id="@+id/nameTxt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@android:color/white"
        android:text="#UI #UX #Psychology"
        android:textSize="17dp"
        android:maxLines="3"
        android:ellipsize="end"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="This star is one of the largest stars ever discovered in the observable universe. Amazing stuff. "
        android:layout_marginTop="1dp"
        android:textColor="#77FFFFFF"
        android:textSize="15dp"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginTop="10dp">

        <TextView
            android:id="@+id/commentsTxt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="102"
            android:gravity="center_vertical"
            android:drawableLeft="@drawable/ic_chat_black_24dp"
            android:drawablePadding="7dp"
            android:drawableTint="@android:color/white"
            android:textColor="@android:color/white"
            android:textSize="15dp"
            />

        <TextView
            android:id="@+id/favoritesTxt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="12K"
            android:gravity="center_vertical"
            android:drawableLeft="@drawable/ic_favorite_black_24dp"
            android:drawablePadding="7dp"
            android:drawableTint="@android:color/white"
            android:textColor="@android:color/white"
            android:textSize="15dp"
            android:layout_marginLeft="25dp"
            />

        <TextView
            android:id="@+id/viewsTxt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="30K"
            android:gravity="center_vertical"
            android:drawableLeft="@drawable/ic_visibility_black_24dp"
            android:drawablePadding="7dp"
            android:drawableTint="@android:color/white"
            android:textColor="@android:color/white"
            android:textSize="15dp"
            android:layout_marginLeft="25dp"
            />

    </LinearLayout>

</LinearLayout>
(c). model_grid.xml

To model a single item to be rendered as part of the list of items in our recyclerview. Will be inflated when we are using GridLayoutManager.

<?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"
    android:background="@color/colorAccent"
    android:transitionName="questionTransition"
    android:padding="10dp"
    android:layout_marginTop="10dp"
    android:layout_marginLeft="10dp"
    android:layout_marginRight="10dp"
    android:id="@+id/layoutQuestion_item"
    android:clickable="true">

    <TextView
        android:id="@+id/nameTxt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@android:color/white"
        android:text="NAME"
        android:textSize="17dp"
        android:maxLines="3"
        android:ellipsize="end"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="This star is one of the largest stars ever discovered in the observable universe. Amazing stuff. "
        android:layout_marginTop="1dp"
        android:textColor="#77FFFFFF"
        android:textSize="12dp"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginTop="10dp">

        <TextView
            android:id="@+id/commentsTxt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="82"
            android:gravity="center_vertical"
            android:drawableLeft="@drawable/ic_chat_black_24dp"
            android:drawablePadding="2dp"
            android:drawableTint="@android:color/white"
            android:textColor="@android:color/white"
            android:textSize="9dp"
            />

        <TextView
            android:id="@+id/favoritesTxt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="2K"
            android:gravity="center_vertical"
            android:drawableLeft="@drawable/ic_favorite_black_24dp"
            android:drawablePadding="2dp"
            android:drawableTint="@android:color/white"
            android:textColor="@android:color/white"
            android:textSize="9dp"
            android:layout_marginLeft="2dp"
            />

        <TextView
            android:id="@+id/viewsTxt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="10K"
            android:gravity="center_vertical"
            android:drawableLeft="@drawable/ic_visibility_black_24dp"
            android:drawablePadding="2dp"
            android:drawableTint="@android:color/white"
            android:textColor="@android:color/white"
            android:textSize="9dp"
            android:layout_marginLeft="2dp"
            />

    </LinearLayout>

</LinearLayout>

Our Classes

Start by creating the following three classes:

  1. Star.java
  2. MainActivity.java
  3. SortedListAdapter.java
(a). Star.java

This class will represent our data object. We will be showing lists of stars in our recyclerview.

Make sure you have our Star class:

public class Star {
    //...
}

In the Star class add the following:

    String name;
    int comments,favorites,views;

    public Star(String name,int comments,int favorites,int views) {
        this.name = name;
        this.comments=comments;
        this.favorites=favorites;
        this.views=views;
    }

You can see in the above we have defined one string and three integers. Those will be the properties of our Star object. We will sort based on those properties.
We have also created a constructor to receive those properties.

Then add our gettes and setters:

public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public int getComments() {
        return comments;
    }

    public void setComments(int comments) {
        this.comments = comments;
    }

    public int getFavorites() {
        return favorites;
    }

    public void setFavorites(int favorites) {
        this.favorites = favorites;
    }

    public int getViews() {
        return views;
    }

    public void setViews(int views) {
        this.views = views;
    }
(b). SortedListAdapter.java

Make the SortedListAdapter class extend the RecyclerView.Adapter class:

public class SortedListAdapter extends RecyclerView.Adapter<SortedListAdapter.ViewHolder> {

Then add the following:

    SortedList<Star> stars;
    private int LAYOUT = R.layout.model;

    public SortedListAdapter(int layout) {
        this.LAYOUT = layout;
        sort(true,"NAME");
    }

You can see we’ve defined a SortedList class to contain our stars. Then an integer to hold the layout we will be inflating.

The constructor is receiving the layout and assigning it to the local variable. Then we are invoking a method known as sort(), well we wil be defining that method shortly.

Let’s go ahead and define that sort method:

    public void sort(final Boolean ascending, final String property){
        stars = new SortedList<>(Star.class, new SortedList.Callback<Star>() {
            @Override
            public int compare(Star star1, Star star2) {
                if(ascending){
                    if(property.equalsIgnoreCase("COMMENTS")){
                        return String.valueOf(star1.getComments()).compareTo(String.valueOf(star2.getComments()));
                    }else if(property.equalsIgnoreCase("FAVORITES")){
                        return String.valueOf(star1.getFavorites()).compareTo(String.valueOf(star2.getFavorites()));
                    }else if(property.equalsIgnoreCase("VIEWS")){
                        return String.valueOf(star1.getViews()).compareTo(String.valueOf(star2.getViews()));
                    }
                    return star1.getName().compareTo(star2.getName());
                }else{
                    if(property.equalsIgnoreCase("COMMENTS")){
                        return String.valueOf(star2.getComments()).compareTo(String.valueOf(star1.getComments()));
                    }else if(property.equalsIgnoreCase("FAVORITES")){
                        return String.valueOf(star2.getFavorites()).compareTo(String.valueOf(star1.getFavorites()));
                    }else if(property.equalsIgnoreCase("VIEWS")){
                        return String.valueOf(star2.getViews()).compareTo(String.valueOf(star1.getViews()));
                    }
                    return star2.getName().compareTo(star1.getName());
                }
            }

            @Override
            public void onChanged(int position, int count) {
                notifyItemRangeChanged(position, count);
            }

            @Override
            public boolean areContentsTheSame(Star star1, Star star2) {
                return star1.getName().equals(star2.getName());
            }

            @Override
            public boolean areItemsTheSame(Star star1, Star star2) {
                return star1.getName().equals(star2.getName());
            }

            @Override
            public void onInserted(int position, int count) {
                notifyItemRangeInserted(position, count);
            }

            @Override
            public void onRemoved(int position, int count) {
                notifyItemRangeRemoved(position, count);
            }

            @Override
            public void onMoved(int fromPosition, int toPosition) {
                notifyItemMoved(fromPosition, toPosition);
            }
        });

    }

You can see it’s receiving two parameters:

  1. boolean value – The sort direction(ascending/descending)
  2. property – Property or field to sort.

You can also we have overrided a couple of methods that allow us react to changes to our sort order.

We will then create a method to add data to our SortedList:

    public void addAll(List<Star> starList) {
        stars.beginBatchedUpdates();
        for (int i = 0; i < starList.size(); i++) {
            stars.add(starList.get(i));
        }
        stars.endBatchedUpdates();
    }

We also have methods to get as well clear that SortedList:

    public Star get(int position) {
        return stars.get(position);
    }

    public void clear() {
        stars.beginBatchedUpdates();
        //remove items at end, to avoid unnecessary array shifting
        while (stars.size() > 0) {
            stars.removeItemAt(stars.size() - 1);
        }
        stars.endBatchedUpdates();
    }

FULL CODE: SortedListAdapter.java

package info.camposha.mrsortedlist;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SortedList;

import java.util.List;

public class SortedListAdapter extends RecyclerView.Adapter<SortedListAdapter.ViewHolder> {

    SortedList<Star> stars;
    private int LAYOUT = R.layout.model;

    public SortedListAdapter(int layout) {
        this.LAYOUT = layout;
        sort(true,"NAME");
    }

    public void sort(final Boolean ascending, final String property){
        stars = new SortedList<>(Star.class, new SortedList.Callback<Star>() {
            @Override
            public int compare(Star star1, Star star2) {
                if(ascending){
                    if(property.equalsIgnoreCase("COMMENTS")){
                        return String.valueOf(star1.getComments()).compareTo(String.valueOf(star2.getComments()));
                    }else if(property.equalsIgnoreCase("FAVORITES")){
                        return String.valueOf(star1.getFavorites()).compareTo(String.valueOf(star2.getFavorites()));
                    }else if(property.equalsIgnoreCase("VIEWS")){
                        return String.valueOf(star1.getViews()).compareTo(String.valueOf(star2.getViews()));
                    }
                    return star1.getName().compareTo(star2.getName());
                }else{
                    if(property.equalsIgnoreCase("COMMENTS")){
                        return String.valueOf(star2.getComments()).compareTo(String.valueOf(star1.getComments()));
                    }else if(property.equalsIgnoreCase("FAVORITES")){
                        return String.valueOf(star2.getFavorites()).compareTo(String.valueOf(star1.getFavorites()));
                    }else if(property.equalsIgnoreCase("VIEWS")){
                        return String.valueOf(star2.getViews()).compareTo(String.valueOf(star1.getViews()));
                    }
                    return star2.getName().compareTo(star1.getName());
                }
            }

            @Override
            public void onChanged(int position, int count) {
                notifyItemRangeChanged(position, count);
            }

            @Override
            public boolean areContentsTheSame(Star star1, Star star2) {
                return star1.getName().equals(star2.getName());
            }

            @Override
            public boolean areItemsTheSame(Star star1, Star star2) {
                return star1.getName().equals(star2.getName());
            }

            @Override
            public void onInserted(int position, int count) {
                notifyItemRangeInserted(position, count);
            }

            @Override
            public void onRemoved(int position, int count) {
                notifyItemRangeRemoved(position, count);
            }

            @Override
            public void onMoved(int fromPosition, int toPosition) {
                notifyItemMoved(fromPosition, toPosition);
            }
        });

    }

    public void addAll(List<Star> starList) {
        stars.beginBatchedUpdates();
        for (int i = 0; i < starList.size(); i++) {
            stars.add(starList.get(i));
        }
        stars.endBatchedUpdates();
    }

    public Star get(int position) {
        return stars.get(position);
    }

    public void clear() {
        stars.beginBatchedUpdates();
        //remove items at end, to avoid unnecessary array shifting
        while (stars.size() > 0) {
            stars.removeItemAt(stars.size() - 1);
        }
        stars.endBatchedUpdates();
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext()).inflate(LAYOUT, parent, false);
        return new ViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Star star = stars.get(position);
        holder.nameTxt.setText(star.getName());
        holder.commentsTxt.setText(String.valueOf(star.getComments()));
        holder.favoritesTxt.setText(String.valueOf(star.getFavorites()));
        holder.viewsTxt.setText(String.valueOf((int)(Math.ceil(star.getViews()/1000)))+"K");
    }

    @Override
    public int getItemCount() {
        return stars.size();
    }

    class ViewHolder extends RecyclerView.ViewHolder {

        TextView nameTxt,commentsTxt,favoritesTxt,viewsTxt;

        public ViewHolder(View itemView) {
            super(itemView);
            nameTxt = itemView.findViewById(R.id.nameTxt);
            commentsTxt = itemView.findViewById(R.id.commentsTxt);
            favoritesTxt = itemView.findViewById(R.id.favoritesTxt);
            viewsTxt = itemView.findViewById(R.id.viewsTxt);

        }

    }

}
//end
(c). MainActivity.java

Finally we can put everything together in our MainActivity class:

package info.camposha.mrsortedlist;

import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    RecyclerView recyclerView;
    SortedListAdapter adapter;
    private Boolean ASC = true;
    Spinner sp;
    String SELECTED_PROPERTY = "NAME";

    private List<Star> generateData(){
        List<Star> starList = new ArrayList<>();
        Star s=new Star("Rigel",98,92,9800);
        starList.add(s);
        s=new Star("Arcturus",73,83,7803);
        starList.add(s);
        s=new Star("Deneb",27,37,4283);
        starList.add(s);
        s=new Star("Wezen",36,39,3703);
        starList.add(s);
        s=new Star("Betelgeuse",89,85,9734);
        starList.add(s);
        s=new Star("Eta Carina",84,91,9242);
        starList.add(s);
        s=new Star("Aldebaran",87,83,8604);
        starList.add(s);
        s=new Star("Canopus",83,72,7937);
        starList.add(s);
        s=new Star("Regulus",75,72,6704);
        starList.add(s);
        s=new Star("Sirius",49,57,5294);
        starList.add(s);
        s=new Star("Trappist A",48,46,4635);
        starList.add(s);
        s=new Star("Proxima Centauri",94,92,9252);
        starList.add(s);
        s=new Star("Tau Ceti",15,25,2573);
        starList.add(s);
        s=new Star("Chara",24,28,3108);
        starList.add(s);
        s=new Star("Vega",46,58,5863);
        starList.add(s);
        s=new Star("Alpha Pegasi",57,62,6348);
        starList.add(s);
        s=new Star("Bellatrix",24,35,3628);
        starList.add(s);
        s=new Star("Naos",31,34,1635);
        starList.add(s);
        s=new Star("Hamal",11,14,1023);
        starList.add(s);
        s=new Star("Polaris",63,68,4592);
        starList.add(s);
        s=new Star("Enif",25,23,1292);
        starList.add(s);
        s=new Star("VY Canis Majoris",93,97,9262);
        starList.add(s);
        s=new Star("UY Scuti",76,91,8924);
        starList.add(s);
        s=new Star("Pollux",15,17,1364);
        starList.add(s);
        s=new Star("Archernar",25,24,2734);
        starList.add(s);
        return starList;

    }

    private void prepareSpinner(){
        final String[] properties = {"NAME","COMMENTS","FAVORITES","VIEWS"};
        sp=findViewById(R.id.propertySpinner);
        sp.setAdapter(new ArrayAdapter<String>(this,android.R.layout.simple_dropdown_item_1line,
                properties));
        sp.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                SELECTED_PROPERTY=properties[position];
                adapter.sort(ASC,SELECTED_PROPERTY);
                adapter.addAll(generateData());
                adapter.notifyDataSetChanged();
            }

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

            }
        });

    }

    private void toggleButtons(){
        final Button ascBtn = findViewById(R.id.ascendigBtn);
        final Button descBtn = findViewById(R.id.sortByCommentsBtn);

        ascBtn.setBackgroundColor(getResources().getColor(android.R.color.holo_red_light));
        descBtn.setBackgroundColor(getResources().getColor(R.color.colorAccent));

        ascBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ASC = true;
                ascBtn.setBackgroundColor(getResources().getColor(android.R.color.holo_red_light));
                descBtn.setBackgroundColor(getResources().getColor(R.color.colorAccent));

                recyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this));

                adapter = new SortedListAdapter(R.layout.model);
                recyclerView.setAdapter(adapter);

                adapter.sort(true,SELECTED_PROPERTY);
                adapter.addAll(generateData());
                adapter.notifyDataSetChanged();
            }
        });
        descBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ASC = false;
                ascBtn.setBackgroundColor(getResources().getColor(R.color.colorAccent));
                descBtn.setBackgroundColor(getResources().getColor(android.R.color.holo_red_light));

                recyclerView.setLayoutManager(new GridLayoutManager(MainActivity.this,2));
                adapter = new SortedListAdapter(R.layout.model_grid);
                recyclerView.setAdapter(adapter);

                adapter.sort(false,SELECTED_PROPERTY);
                adapter.addAll(generateData());
                adapter.notifyDataSetChanged();

            }
        });

    }

    private void bindData(){
        recyclerView = findViewById(R.id.myRecyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        adapter = new SortedListAdapter(R.layout.model);
        recyclerView.setAdapter(adapter);

        adapter.addAll(generateData());

    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        bindData();
        prepareSpinner();
        toggleButtons();
    }
}
//end

Resources

No. Site Action
1. Github Browse
2. Github Download
3. YouTube Watch
4. Android Documentation Reference
Share