Reputation: 286
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
Reputation: 156
Some things I discovered or worked out about using a SortedList with a RecyclerView, particularly relating to background tasks updating the view...
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.
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]
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.
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]
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.
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
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
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