Devs
Devs

Reputation: 286

how to sort the data in RecyclerView

I might seems a repetition but i am really unable to find a better way to sort the data in recycler view containing cardview.

Below is the snippet of recyclerview in my mainactivity

 mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview);
    LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
    linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
    mRecyclerView.setLayoutManager(linearLayoutManager);


    mAdapter = new PickUpPointAdapter(testData);
    mRecyclerView.setAdapter(mAdapter);
    mCardView=(CardView)findViewById(R.id.searchcard);


    mCardView.setOnClickListener(new View.OnClickListener() {
        @Override public void onClick(View v) {
            // item clicked
            Intent search = new Intent( AppController.getAppContext(), TabLayoutActivity.class);
            search.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            startActivity(search);
        }
    });

    Map<String,String> params = new HashMap<String,String>();
    JsonObjectRequest NearbyPickUpPointReq = new JsonObjectRequest (Request.Method.GET,url_searchNearBybusStopServlet,new JSONObject(params), new Response.Listener<JSONObject>() {
        @Override
        public void onResponse(JSONObject response) {
            Log.d("Tag", response.toString());
            // hidePDialog();
            try {
                JSONArray obj1 = response.getJSONArray("pickuppoint");
                testData.clear();
                // Parsing json
                for (int i = 0; i < obj1.length(); i++) {


                    JSONObject obj = (JSONObject)obj1.get(i);
                    SearchResults srchresult = new SearchResults();
                    srchresult.setPickUpPoint(obj.getString("PickupPoint"));
                    srchresult.setRouteName(obj.getString("RouteName"));
                    srchresult.setLatitude(obj.getDouble("Latitude"));
                    srchresult.setLongitude(obj.getDouble("Longitude"));
                    srchresult.setWalkingTime(obj.getInt("WalkingTime"));
                    srchresult.setRoute2("-");
                    mapMarker( srchresult.getLatitude(), srchresult.getLongitude(),srchresult.getPickUpPoint());
                    String[] RouteArray = srchresult.getRouteName().split(",");
                    //  Log.e(TAG, "Error: " + RouteArray.length);
                    srchresult.setRoute1( RouteArray[0]);
                    if(RouteArray.length>1)
                        srchresult.setRoute2(RouteArray[1]);
                    testData.add(srchresult);
                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
            // notifying list adapter about data changes
            // so that it renders the list view with updated data


            mAdapter.notifyDataSetChanged();
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            //   VolleyLog.d(TAG, "Error: " + error.getMessage());
            // hidePDialog();

        }

    });

    // Adding request to request queue

    AppController.getInstance().addToRequestQueue(NearbyPickUpPointReq);


    }

Here is the customadapter

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

ArrayList<SearchResults> mItems;
public int TAG=0;


public PickUpPointAdapter(ArrayList<SearchResults> mItems) {
    this.mItems=mItems;
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
    View v = LayoutInflater.from(viewGroup.getContext())
            .inflate(R.layout.nearby_busstop_list_layout, viewGroup, false);
    ViewHolder viewHolder = new ViewHolder(v);
    return viewHolder;
}

@Override
public void onBindViewHolder(final ViewHolder viewHolder, int i) {
    final SearchResults routename = mItems.get(i);
    viewHolder.tv_pickuppointname.setText(routename.getPickUpPoint());
    viewHolder.tv_walkingtime.setText(String.valueOf(routename.getWalkingTime())+"min walk ");
    viewHolder.tv_allroutename.setText(routename.getRouteName());
    viewHolder.tv_route1.setText(routename.getRoute1());
    viewHolder.tv_route2.setText(routename.getRoute2());

    Log.e("TAG","i value="+ i);
   // if(i==mItems.size()-1)
      // viewHolder.seperator.setVisibility(View.INVISIBLE);
       viewHolder.mView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Context context = v.getContext();
            Intent intent = new Intent(context,BusStopDetailsnew.class);
            intent.putExtra("pickup", routename.getPickUpPoint());
            intent.putExtra("route", routename.getRouteName());
            intent.putExtra("walk", routename.getWalkingTime());
            intent.putExtra("lat", routename.getLatitude());
            intent.putExtra("lon", routename.getLongitude());
            context.startActivity(intent);
        }
    });

}

@Override
public int getItemCount() {
    Log.e("TAG","item size"+ mItems.size());
    return mItems.size();

}

class ViewHolder extends RecyclerView.ViewHolder{


    public TextView tv_pickuppointname;
    TextView tv_walkingtime;
    TextView tv_allroutename;
    TextView tv_route1;
    TextView tv_route2;
    public View seperator;
    public View mView;

    public ViewHolder(View itemView) {
        super(itemView);
        mView=itemView;
        tv_pickuppointname = (TextView)itemView.findViewById(R.id.BusstopName);
        tv_walkingtime = (TextView) itemView.findViewById(R.id.walktime);
        tv_allroutename = (TextView) itemView.findViewById(R.id.allbusRoute);
        tv_route1 = (TextView) itemView.findViewById(R.id.RouteName1);
        tv_route2 = (TextView) itemView.findViewById(R.id.RouteName2);
        seperator=(View)itemView.findViewById(R.id.seperator);
    }
    }

And finally the getter and setters method

public class SearchResults {
private String PickUpPoint="";
private int WalkingTime=0;
private String RouteName = "";
private String Route1="";
private String Route2="";
private Double Latitude = null;
private Double Longitude = null;




//walking time
public int getWalkingTime() {
    return WalkingTime;
}

public void setWalkingTime(int WalkingTime) {
    this.WalkingTime = WalkingTime;
}

//Route1
public String getRoute1() {
    return Route1;
}

public void setRoute1(String Route1) {
    this.Route1 = Route1;
}

//Route2
public String getRoute2() {
    return Route2;
}

public void setRoute2(String Route2) {
    this.Route2 = Route2;
}

//PickUpPoint
public void setPickUpPoint(String PickUpPoint) {
    this.PickUpPoint = PickUpPoint;
}

public String getPickUpPoint() {
    return PickUpPoint;
}
//RouteName ArrayList
public void setRouteName(String RouteName) {
    this.RouteName = RouteName;
}

public String getRouteName() {
    return RouteName;
}
//Latitude
public void setLatitude(Double Latitude) {
    this.Latitude = Latitude;
}

public Double getLatitude() {
    return Latitude;
}
//Longitude
public void setLongitude(Double Longitude) {
    this.Longitude = Longitude;
}

public Double getLongitude() {
    return Longitude;
}
}

I want to sort the list according to the walking time. Can anyone help me with this?

Upvotes: 5

Views: 26095

Answers (3)

user998303
user998303

Reputation: 156

Some things I discovered or worked out about using a SortedList with a RecyclerView, particularly relating to background tasks updating the view...

  1. When updating an item in the RecyclerView, it is searched for based on the sort key. So you must be extra careful if your SortedList includes Objects where a key field is updated by the background process.

  2. You need a unique ID for each item, which must also be part of the sort key, so that you can distinguish two Objects. This ID must never be changed.

So, if I'm displaying vehicles ordered by distance, the sort key is (distance, vehicleId).

However, if my background task changes the distance of the vehicle, that Object is no longer findable in the SortedList, and attempting to do so would result in an "Inconsistent Data" Exception. The trick here is to find the Object before changing its distance field.

[Edited after I found that my SortedListAdapter still didn't work in my actual application... these next points were not in the original post]

  1. The path of an insertion is as follows (changes and removals are similar):
  • SortedList.add(), this triggers
  • RecyclerView calls back to
  • SortedList.onInserted(), which then posts to
  • RecyclerView.notifyItemInserted(), which (on the UI Thread) calls
  • Adapter.onBindViewHolder() and eventually
  • RecyclerView.onLayout()
  • But that is not the end of the process... there are still accesses to the SortedList
  1. SortedList is implemented as an array. So if you have a large list of large Objects and frequently insert/remove/change location, there will be a large amount of work done in shifting chunks of memory around.

  2. A RecyclerView can only be changed by the Thread that created it, so your background threads need to use the RecyclerView.post method (or a Handler.post) to do the update.

[Edited again after another week's worth of digging]

  1. Whilst the SortedList is Thread safe, the RecyclerView does not access it in a safe manner. The problem is that the RecyclerView accesses the SortedList on the UI Thread after the notifyItemRangeInserted() and similar methods have returned back to the adapter. Apparently the notifyItemRangeInserted() and similar methods just queue up changes which the UI thread will subsequently process. This means that even if you synchronize and/or lock and/or single-thread these method calls, subsequent updates of the SortedList can happen before the first update has completed.

For example, if you add an item to the SortedList, and then, even after notifyItemRangeInserted() has returned, add another item, you can still get the dreaded "IndexOutOfBoundsException: Inconsistency Detected" Exception. The time window for concurrency seems to be 250ms or so after the onLayout() has returned if you're debugging, less than 50ms if not. But I suspect that will vary depending on how long the list is, and how complex the changes.

  1. The AdapterHelper.Callback class appears to do the next stage of updating the RecyclerView. It is probably worth investigating this, with a view to locking the SortedList until it has completed. If you could do this, it might finally resolve this.

FWIW, Below is my original SortedListAdapter class... it doesn't solve the whole problem, but is presented as a starting point for others. I will update it if/when I get more figured out... is there a callback which indicates that the RecyclerView is finished?

package com.example.sortedrecycler;

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

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

import java.util.ArrayList;

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

    private final SortedList<Vehicle> sortedList;

    public SortedListAdapter(ArrayList<Vehicle> initList) {
        sortedList = new SortedList<>(Vehicle.class, new SortedList.Callback<>() {
            public int compare(Vehicle v1, Vehicle v2) {
                var distComp = Double.compare(v1.distance, v2.distance);
                if (distComp != 0) return distComp;
                return Integer.compare(v1.id, v2.id);
            }

            public boolean areContentsTheSame(Vehicle oldItem, Vehicle newItem) {
                return oldItem.id == newItem.id
                        && oldItem.distance == newItem.distance
                        && oldItem.name.equals(newItem.name);
            }

            public boolean areItemsTheSame(Vehicle item1, Vehicle item2) {
                return item1.id == item2.id;
            }

            @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);
            }

            @Override
            public void onChanged(int position, int count) {
                notifyItemRangeChanged(position, count);
            }
        });
        sortedList.addAll(initList);
    }

    @NonNull
    public VehicleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new VehicleViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.items_list, parent, false));
    }

    public void onBindViewHolder(VehicleViewHolder holder, int position) {
        Vehicle vehicle = sortedList.get(position);
        holder.name.setText(vehicle.name);
        holder.distance.setText(String.format("%.0f", vehicle.distance));
    }

    public int getItemCount() {
        return sortedList.size();
    }

    public void add(Vehicle vehicle) {
        vehicle.displayed = new Vehicle(vehicle);
        sortedList.add(vehicle.displayed);
    }

    public void remove(Vehicle vehicle) {
        sortedList.remove(vehicle.displayed);
    }

    public void update(Vehicle vehicle) {
        var old = sortedList.indexOf(vehicle.displayed);
         if (old == -1)
            add(vehicle);
        else {
             vehicle.displayed = new Vehicle(vehicle);
             sortedList.updateItemAt(old, vehicle.displayed);
        }
    }

    public static class VehicleViewHolder extends RecyclerView.ViewHolder {
        private TextView name;
        private TextView distance;

        public VehicleViewHolder(View itemView) {
            super(itemView);
            name = itemView.findViewById(R.id.tvIdName);
            distance = itemView.findViewById(R.id.tvDistance);
        }
    }
}

This will do all the work to decide if your distance has changed and therefore the list position, and rearrange the RecyclerView as needed, and perform the necessary callbacks. It also keeps a copy of the currently displayed state of the Vehicle (as field 'displayed' in Vehicle) so that the background task can safely change the distance of this particular Object.

So here's a fragment (because I was showing my RecyclerView in a Fragment) simulating a background task by me clicking buttons. Note the new Vehicle constructed as a copy of the :

package com.example.sortedrecycler;

import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

public class FirstFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_first, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState)
    {
        super.onViewCreated(view, savedInstanceState);

        // getting the employeelist
        ArrayList<Vehicle> employeelist = Constants.getVehicles();

        // Assign employeelist to ItemAdapter
        SortedListAdapter itemAdapter = new SortedListAdapter(employeelist);

        // Set the LayoutManager that
        // this RecyclerView will use.
        RecyclerView recyclerView = view.findViewById(R.id.recycleView);
        recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));

        // adapter instance is set to the
        // recyclerview to inflate the items.
        recyclerView.setAdapter(itemAdapter);

        // Get a reference to the "btn_frag" button from the layout
        Button btn = view.findViewById(R.id.btn_add);

        Vehicle backgroundVehicle = new Vehicle(4, "Frank",3);
        final int[] x = {0};
        btn.setOnClickListener(v -> {
             new Thread(new Runnable() {
                @Override
                public void run() {
                    switch ((x[0]++)) {
                        case 0:
                            backgroundVehicle.displayed = new Vehicle(backgroundVehicle);
                            itemAdapter.add(backgroundVehicle.displayed);
                            btn.setText("Distance 6");
                            break;
                        case 1:
                            backgroundVehicle.distance = 6;
                            itemAdapter.update(backgroundVehicle);
                            btn.setText("Distance 25");
                        break;
                        case 2:
                            backgroundVehicle.distance = 25;
                            itemAdapter.update(backgroundVehicle);
                            btn.setText("End");
                            break;
                        default: Log.e("x", "Out of actions");
                    }
                }
            }).run();
         });
    }
}
class Constants {
    // ArrayList and return the ArrayList
    public static ArrayList<Vehicle> getVehicles() {
        // create an ArrayList of type Employee class
        ArrayList<Vehicle> vehicleList = new ArrayList<>();
        vehicleList.add(new Vehicle(1, "Batmobile", 5));
        vehicleList.add(new Vehicle(2, "Herbie", 10));
        vehicleList.add(new Vehicle(3, "Eleanor", 15));
        vehicleList.add(new Vehicle(4, "Wet Nellie", 20));
        return vehicleList;
    }
}

Upvotes: 0

Sagar
Sagar

Reputation: 24907

You can implement SortedList which is more efficient. SortedList is available in Support library V7 (android.support.v7.util). Based on the documentation:

It keeps items ordered using the SortedList.Callback.compare(Object, Object) method and uses binary search to retrieve items.

It more efficient and provides better control over add, insert & delete in sorted list. All you have to do is switch our list to SortedList in our adapter and override 3 adapter callback methods: compare(), areContentsSame() and areItemsSame(). You can find more details about its usage here.

Upvotes: 2

Ashish Agrawal
Ashish Agrawal

Reputation: 1977

//you can use comparable interface in bean and compare with particular field

public class SearchResults implements Comparable{

    private int id;

    public int getId() {
        return id;
    }

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

    @Override
    public int compareTo(Object another) {
        if(((SearchDetail)another).getId() > id){
            return 1;
        }if(((SearchDetail)another).getId() == id){
            return 0;
        }else{
            return -1;
        }
        return 0;
    }
}

please compare that field to which you want to compare

Edited-

mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(linearLayoutManager);


mAdapter = new PickUpPointAdapter(testData);
mRecyclerView.setAdapter(mAdapter);
mCardView=(CardView)findViewById(R.id.searchcard);


mCardView.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        // item clicked
        Intent search = new Intent( AppController.getAppContext(), TabLayoutActivity.class);
        search.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        startActivity(search);
    }
});

Map<String,String> params = new HashMap<String,String>();
JsonObjectRequest NearbyPickUpPointReq = new JsonObjectRequest (Request.Method.GET,url_searchNearBybusStopServlet,new JSONObject(params), new Response.Listener<JSONObject>() {
    @Override
    public void onResponse(JSONObject response) {
        Log.d("Tag", response.toString());
        // hidePDialog();
        try {
            JSONArray obj1 = response.getJSONArray("pickuppoint");
            testData.clear();
            // Parsing json
            for (int i = 0; i < obj1.length(); i++) {


                JSONObject obj = (JSONObject)obj1.get(i);
                SearchResults srchresult = new SearchResults();
                srchresult.setPickUpPoint(obj.getString("PickupPoint"));
                srchresult.setRouteName(obj.getString("RouteName"));
                srchresult.setLatitude(obj.getDouble("Latitude"));
                srchresult.setLongitude(obj.getDouble("Longitude"));
                srchresult.setWalkingTime(obj.getInt("WalkingTime"));
                srchresult.setRoute2("-");
                mapMarker( srchresult.getLatitude(), srchresult.getLongitude(),srchresult.getPickUpPoint());
                String[] RouteArray = srchresult.getRouteName().split(",");
                //  Log.e(TAG, "Error: " + RouteArray.length);
                srchresult.setRoute1( RouteArray[0]);
                if(RouteArray.length>1)
                    srchresult.setRoute2(RouteArray[1]);
                testData.add(srchresult);
            }
        Collections.sort(testData);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        // notifying list adapter about data changes
        // so that it renders the list view with updated data


        mAdapter.notifyDataSetChanged();
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        //   VolleyLog.d(TAG, "Error: " + error.getMessage());
        // hidePDialog();

    }

});

// Adding request to request queue

AppController.getInstance().addToRequestQueue(NearbyPickUpPointReq);


}

Upvotes: 6

Related Questions