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:

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.

(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.

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:

In the Star class add the following:

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:

(b). SortedListAdapter.java

Make the SortedListAdapter class extend the RecyclerView.Adapter class:

Then add the following:

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:

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:

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

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