How to update item in a recycler view using diff item callback? - android

I'm implementing a recycler view using room database library. I would like to list my items and that they could be edited by clicking. After re-submit the item, go back again to the list and show up the items + changes.
I'm currently using DiffUtil callback to compare the old and the new lists. I think the problem is here. My Dto item has 4 fields.
id title image arraylist
So when the user edits an item, the list should update the UI row (title, image or/and arraylist)
This is my Adapter.
private OnItemClickListener listener;
public ExercicioAdapter() {
super(DIFF_CALLBACK);
}
private static final DiffUtil.ItemCallback<Exercicio> DIFF_CALLBACK = new DiffUtil.ItemCallback<Exercicio>() {
#Override
public boolean areItemsTheSame(#NonNull Exercicio oldExercicio, #NonNull Exercicio newExercicio) {
return oldExercicio.getId() == newExercicio.getId();
}
#Override
public boolean areContentsTheSame(#NonNull Exercicio oldExercicio, #NonNull Exercicio newExercicio) {
return oldExercicio.getTitulo().equals(newExercicio.getTitulo()) &&
Float.compare(oldExercicio.getMaxPeso(), newExercicio.getMaxPeso()) == 0;
}
};
#NonNull
#Override
public MyHolderView onCreateViewHolder(#NonNull ViewGroup parent, int i) {
View view;
LayoutInflater mInflater = LayoutInflater.from(parent.getContext());
view = mInflater.inflate(R.layout.exercicios_item, parent, false);
return new MyHolderView(view);
}
#Override
public void onBindViewHolder(#NonNull MyHolderView exercicioHolder, final int i) {
Exercicio exercicio = getItem(i);
// Mostrar a informacion do obxeto.
ArrayList<Repeticion> repeticions = exercicio.getRepeticions();
float peso_max = 0f;
if (repeticions != null && repeticions.size() > 0) {
for (Repeticion rep : repeticions) {
if (rep.getPeso() > peso_max) {
peso_max = rep.getPeso();
}
}
}
exercicioHolder.nome.setText(exercicio.getTitulo());
String peso_str = exercicioHolder.itemView.getContext().getString(R.string.peso_max);
exercicioHolder.peso.setText(peso_str + " " + Float.toString(peso_max));
if (exercicio.getImaxe() != null && exercicio.getImaxe().length != 0) {
exercicioHolder.imx_exercicio.setImageBitmap(convertByteArrayToBitmap(exercicio.getImaxe()));
}
}
public Exercicio getExercicioAt(int position) {
return getItem(position);
}
public class MyHolderView extends RecyclerView.ViewHolder {
TextView nome;
TextView peso;
ImageView imx_exercicio;
CardView exercicio_cardview;
MyHolderView(#NonNull View itemView) {
super(itemView);
exercicio_cardview = itemView.findViewById(R.id.exercicio_cardview);
nome = itemView.findViewById(R.id.nome);
peso = itemView.findViewById(R.id.peso);
imx_exercicio = itemView.findViewById(R.id.imx_exercicio);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
int position = getAdapterPosition();
if (listener != null && position != RecyclerView.NO_POSITION) {
listener.onItemClick(getItem(position));
}
}
});
}
}
public interface OnItemClickListener {
void onItemClick(Exercicio exercicio);
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
The problem: when an item is updated, changing the title for example, the list gets updated, but the eddited item disappears. If i end the activity and start it again, the item updated is there. So the problem must be the diff callback, but I'm not really able to make it work. Sorry for my English, this is not my main language, thanks all!

Most probably you are not properly using the DiffUtil functionality. You first need to calculate diff and then dispatch updates to adapter after which it will refresh your adapter with the new data. Use DiffUtil.Callback() and not DiffUtil.ItemCallback()
DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
#Override
public int getOldListSize() {
// old list size, which is same as new in your case
}
#Override
public int getNewListSize() {
// new list size, which is same as old in your case
}
#Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition)
}
#Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
}
)
result.dispatchUpdatesTo(this); // this parameter is your Recycler View's adapter
You can read more about Diff Util here and here
Let me know if you still have doubts. Thanks.

Related

Problem of automatic change of data value when scrolling in RecyclerView

I have attached a picture to help you understand my problem.
I'm making an Workout app.
When the button is pressed, the routine is added
In the added routine, it can use the button again to add detailed data (set, lbs, reps).
So, I am using a two type RecyclerView (except footer).
I used DiffUtil to update the items being added.
I found a strange phenomenon while experimenting for testing. (See photo)
It wasn't an error, so I can't figure out what's wrong with it.
After entering the data, when i added a number of detailed items, the value of the data entered first appeared in the items added later.
And if i scrolled after many items were added, the data moved randomly.
However, what is expected is this phenomenon when you add a lot of items or scroll.
What's wrong?
RoutineAdapter.java
public class RoutineAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
final static int TYPE_ROUTINE = 1;
final static int TYPE_ROUTINE_DETAIL = 2;
final static int TYPE_ROUTINE_FOOTER = 3;
private Context context;
private List<Object> mItems = new ArrayList<>();
OnRoutineItemClickListener routinelistener;
OnRoutineAddClickListener routineAddListener;
public void updateRoutineList(List<Object> newRoutineList) {
final RoutineDiffUtil diffCallback = new RoutineDiffUtil(this.mItems, newRoutineList);
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);
this.mItems.clear();
this.mItems.addAll(newRoutineList);
diffResult.dispatchUpdatesTo(this);
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
context = parent.getContext();
View itemView;
if(viewType == TYPE_ROUTINE){
itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.routine_item, parent, false);
return new RoutineViewHolder(itemView);
}
else if(viewType == TYPE_ROUTINE_DETAIL){
itemView = LayoutInflater.from(context).inflate(R.layout.routine_detail_item, parent, false);
return new RoutineDetailViewHolder(itemView);
}
else {
itemView = LayoutInflater.from(context).inflate(R.layout.add_routine_item, parent, false);
return new RoutineAddFooterViewHolder(itemView);
}
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
Object obj;
switch (getItemViewType(position)) {
case TYPE_ROUTINE:
obj = mItems.get(position);
setRoutineData((RoutineViewHolder) holder, (RoutineModel) obj);
break;
case TYPE_ROUTINE_DETAIL:
obj = mItems.get(position);
RoutineDetailModel item = (RoutineDetailModel) obj;
((RoutineDetailViewHolder) holder).setDetailItem(item);
break;
case TYPE_ROUTINE_FOOTER:
break;
}
}
#Override
public int getItemCount() {
if(mItems == null)
return -1;
return mItems.size() + 1; // for footer
}
#Override
public int getItemViewType(int position) {
if(position == mItems.size()) {
return TYPE_ROUTINE_FOOTER;
}
else {
Object obj = mItems.get(position);
if(obj instanceof RoutineModel) {
return TYPE_ROUTINE;
}
else {
// obj instanceof RoutineDetailModel
return TYPE_ROUTINE_DETAIL;
}
}
}
// add routine interface
public interface OnRoutineAddClickListener {
public void onAddRoutineClick();
}
public void setOnAddRoutineClickListener(OnRoutineAddClickListener listener) {
this.routineAddListener = listener;
}
// details add / remove interface
public interface OnRoutineItemClickListener {
public void onAddBtnClicked(int curRoutinePos);
public void onDeleteBtnClicked(int curRoutinePos);
public void onWritingCommentBtnClicked(int curRoutinePos);
}
public void setOnRoutineClickListener(OnRoutineItemClickListener listener) {
this.routinelistener = listener;
}
private class RoutineViewHolder extends RecyclerView.ViewHolder {
public TextView routine;
public Button addSet;
public Button deleteSet;
public Button comment;
public RoutineViewHolder(#NonNull View itemView) {
super(itemView);
initViews();
addSet.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(routinelistener != null && getAdapterPosition() != RecyclerView.NO_POSITION)
routinelistener.onAddBtnClicked(getAdapterPosition());
}
});
deleteSet.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(routinelistener != null && getAdapterPosition() != RecyclerView.NO_POSITION)
routinelistener.onDeleteBtnClicked(getAdapterPosition());
}
});
comment.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(routinelistener != null && getAdapterPosition() != RecyclerView.NO_POSITION)
routinelistener.onWritingCommentBtnClicked(getAdapterPosition());
}
});
}
}
private class RoutineDetailViewHolder extends RecyclerView.ViewHolder {
public TextView set;
public TextView weight;
public RoutineDetailViewHolder(#NonNull View itemView) {
super(itemView);
initViews();
}
private void initViews() {
set = itemView.findViewById(R.id.set);
weight = itemView.findViewById(R.id.weight);
}
private void setDetailItem(RoutineDetailModel item) {
set.setText(item.getSet().toString() + "set");
}
}
}
RoutineDiffUtil.java
public class RoutineDiffUtil extends DiffUtil.Callback {
private List<Object> oldRoutineList;
private List<Object> newRoutineList;
public RoutineDiffUtil(List<Object> oldRoutineList, List<Object> newRoutineList) {
this.oldRoutineList = oldRoutineList;
this.newRoutineList = newRoutineList;
}
#Override
public int getOldListSize() {
return oldRoutineList.size();
}
#Override
public int getNewListSize() {
return newRoutineList.size();
}
#Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
Object oldObj = oldRoutineList.get(oldItemPosition);
Object newObj = newRoutineList.get(newItemPosition);
if (oldObj instanceof RoutineModel && newObj instanceof RoutineModel) {
return ((RoutineModel) oldObj).id == ((RoutineModel) newObj).id;
}
else if (oldObj instanceof RoutineDetailModel && newObj instanceof RoutineDetailModel) {
return ((RoutineDetailModel) oldObj).id == ((RoutineDetailModel) newObj).id;
}
else if(oldObj instanceof RoutineModel && newObj instanceof RoutineDetailModel) {
return false;
}
else {
return false;
}
}
#Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return (oldRoutineList.get(oldItemPosition)).equals(newRoutineList.get(newItemPosition));
}
}
RoutineDetailModel.java
public class RoutineDetailModel {
public int id;
private int set = 1;
private int weight;
public RoutineDetailModel() {
Random random = new Random();
this.id = random.nextInt();
}
public RoutineDetailModel(int set) {
Random random = new Random();
this.id = random.nextInt();
this.set = set+1;
}
public Integer getSet() {
return set;
}
public int getId() {
return id;
}
#Override
public int hashCode() {
return Objects.hash(set, weight);
}
#Override
public boolean equals(#Nullable Object obj) {
if(obj != null && obj instanceof RoutineDetailModel) {
RoutineDetailModel model = (RoutineDetailModel) obj;
if(this.id == model.getId()) {
return true;
}
}
return false;
}
}
First of all, you should use ListAdapter class with diff util. The problem with your adapter is that, Recycler view recycles views again and again. That is to say that when you entered a text for your first item, this item is used for other views. To solve it after any text change you should keep this text in your model class then you should set this text to the field in onBind() method. To sum up, recycler view uses same view for different items so any data related to any item should be kept in a model and the model should be set to the view in onBind().

RecyclerView items disappear after restarting app

I was previously using RecyclerView Adapter and decided to switch to ListAdapter to make use of DiffUtils for the animations. Everything seem to go smoothly at first until I cleared my app from the task switcher (Memory). Once I opened my app again all the items were gone and this is with a SQLite database implemented. Turns out I have to add a new item unto the RecyclerView for the previous items to show up. This happened after I switched over to ListAdpater and I think that's the culprit. Any help would be appreciated. Thanks in advance.
My Adapter Class
public class CourseAdapter extends ListAdapter<Course, CourseAdapter.CourseHolder> {
private OnItemClickListener listener;
public CourseAdapter() {
super(DIFF_CALLBACK);
}
private static final DiffUtil.ItemCallback<Course> DIFF_CALLBACK = new DiffUtil.ItemCallback<Course>() {
#Override
public boolean areItemsTheSame(#NonNull Course oldItem, #NonNull Course newItem) {
return oldItem.getId() == newItem.getId();
}
#Override
public boolean areContentsTheSame(#NonNull Course oldItem, #NonNull Course newItem) {
return oldItem.getName().equals(newItem.getName()) &&
oldItem.getBuilding().equals(newItem.getBuilding()) &&
oldItem.getRoom().equals(newItem.getRoom()) &&
oldItem.getDays().equals(newItem.getDays()) &&
oldItem.getStartTime().equals(newItem.getStartTime()) &&
oldItem.getEndTime().equals(newItem.getEndTime()) &&
oldItem.getProfName().equals(newItem.getProfName()) &&
oldItem.getEmail().equals(newItem.getEmail()) &&
oldItem.getProfBuilding().equals(newItem.getProfBuilding()) &&
oldItem.getProfRoom().equals(newItem.getProfRoom()) &&
oldItem.getProfDays().equals(newItem.getProfDays()) &&
oldItem.getProfStartTime().equals(newItem.getProfStartTime()) &&
oldItem.getProfEndTime().equals(newItem.getProfEndTime()) &&
oldItem.getColor() == newItem.getColor();
}
};
#NonNull
#Override
public CourseHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.course_item, parent, false);
return new CourseHolder(itemView);
}
#Override
public void onBindViewHolder(#NonNull CourseHolder holder, int position) {
Course currentCourse = getItem(position);
holder.card.setCardBackgroundColor(currentCourse.getColor());
holder.textViewName.setText(currentCourse.getName());
holder.textViewDays.setText(currentCourse.getDays());
String startTime = currentCourse.getStartTime();
String endTime = currentCourse.getEndTime();
if (startTime.isEmpty() && endTime.isEmpty()) {
holder.textViewTime.setText("");
} else {
holder.textViewTime.setText(startTime + "-" + endTime);
}
}
public Course getCourseAt(int position) {
return getItem(position);
}
class CourseHolder extends RecyclerView.ViewHolder {
private TextView textViewName;
private TextView textViewDays;
private TextView textViewTime;
private MaterialCardView card;
public CourseHolder(#NonNull View itemView) {
super(itemView);
textViewName = itemView.findViewById(R.id.text_view_name);
textViewDays = itemView.findViewById(R.id.text_view_days);
textViewTime = itemView.findViewById(R.id.text_view_time);
card = itemView.findViewById(R.id.card_view_courses);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = getAdapterPosition();
if (listener != null && position != RecyclerView.NO_POSITION) {
listener.onItemClick(getItem(position), position, textViewName);
}
}
});
}
}
public interface OnItemClickListener {
void onItemClick(Course course, int position, TextView title);
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
}
In Main Activity
RecyclerView recyclerView = findViewById(R.id.recycler_view_courses);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setHasFixedSize(true);
CourseAdapter adapter = new CourseAdapter();
recyclerView.setAdapter(adapter);
viewModel = ViewModelProviders.of(this).get(ViewModel.class);
viewModel.getAllCourses().observe(this, new Observer<List<Course>>() {
#Override
public void onChanged(List<Course> courses) {
adapter.submitList(courses);
}
});

Filtering RecyclerView with 4 different Spinner

I am currently trying to implement a search function for RecyclerView containing custom items in form of CardViews with 4 different TextViews.
I want to use 4 different spinner elements, each covering one textView.
For now I tried the search with one spinner only, but it does not work.
Custom RecyclerView Adapter:
public class WorkersAdapter extends RecyclerView.Adapter<WorkersAdapter.ViewHolder> {
private List<Worker> workers, filtered;
private Context c;
private int lastPosition = -1;
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView levelracein, nodein, cityin;
public ImageView iv;
public ViewHolder(View view) {
super(view);
levelracein = (TextView) view.findViewById(R.id.levelracein);
cityin = (TextView) view.findViewById(R.id.cityin);
nodein = (TextView) view.findViewById(R.id.nodein);
}
}
public WorkersAdapter(Context c, List<Worker> workers) {
this.c = c;
this.workers = workers;
this.filtered = new ArrayList<>();
this.filtered.addAll(this.workers);
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.worker_list_item, parent, false);
return new ViewHolder(itemView);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
int pos = position;
Worker worker = filtered.get(position);
holder.levelracein.setText(worker.getLevel() + " " + worker.getRace());
holder.cityin.setText(worker.getCity());
holder.nodein.setText(worker.getNode());
setAnimation(holder.itemView, position);
}
#Override
public int getItemCount() {
return workers != null? workers.size() : 0;
}
private void setAnimation(View viewToAnimate, int position)
{
if (position > lastPosition)
{
Animation animation = AnimationUtils.loadAnimation(c, android.R.anim.slide_in_left);
viewToAnimate.startAnimation(animation);
lastPosition = position;
}
}
public void filter(final String level, final String race, final String city, final String node) {
new Thread(new Runnable() {
#Override
public void run() {
filtered.clear();
if (level.equals("None")) {
filtered.addAll(workers);
} else {
for (Worker worker : workers) {
if (worker.getLevel().toLowerCase().contains(level.toLowerCase())) {
filtered.add(worker);
}
}
}
((Activity) c).runOnUiThread(new Runnable() {
#Override
public void run() {
// Notify the List that the DataSet has changed...
notifyDataSetChanged();
}
});
}
}).start();
}
}
The Spinner listener:
levelspinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
onQueryTextChange(levelspinner.getSelectedItem().toString(),
racespinner.getSelectedItem().toString(),
cityspinner.getSelectedItem().toString(),
nodespinner.getSelectedItem().toString());
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
The function to feed the search in RecyclerView:
public boolean onQueryTextChange(String level, String race, String city, String node) {
wa.filter(level, race, city, node); //wa -> WorkersAdapter
return true;
}
The question is,
How to implement a search which will add all the different spinner selections and search more precise with every spinner item selected?
Why does my RecyclerView still display cardViews which are empty after selecting?
Change your getItemCount() to below code on Adapter class , you used here two ArralList reference when you search it will showing result by your filtered ArrayList reference . so you have to return filtered.size()
#Override
public int getItemCount() {
return filtered!= null? filtered.size() : 0;
}
As per your Comment you want search all of these spinners in single filter change with below code
for (Worker worker : workers) {
if (worker.getLevel().toLowerCase().contains(level.toLowerCase()) || 2nd searchitem || 3rd searchitem || 4th searchitem ) {
filtered.add(worker);
}
}

how to make the header of Recycle view invisible

I have a Recyclerview with header view as slider images and remaining view as normal recycler items.I am wondering if there is any way around to make the header view invisible depending upon some sort of condition.The recycler view consists of two separate layout files for this purpose: layout1 for header items and layout2 for normal recycler items and adapter will pick a layout and binds corresponding data at runtime.
This is my RecyclerView adapter RestaurantAdapter.java
public class RestaurantAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final String TAG = RestaurantAdapter.class.getName();
private List<Restaurant> mList;
private Context mContext;
private RestaurantType mRestaurantType;
private static final int RECYCLER_HEADER=0,RECYCLER_ITEMS=1;
private LayoutInflater inflater;
private SlideItemViewHolder slideItemViewHolder;
private List<ImageSliderPOJO> mData;
public RestaurantAdapter(Context context, List<Restaurant> list, RestaurantType restaurantType) {
this.mContext = context;
this.mList = list;
this.mRestaurantType = restaurantType;
inflater=LayoutInflater.from(context);
}
public void updateAdapter(List<ImageSliderPOJO> data){
this.mData = data;
this.notifyDataSetChanged();
}
#Override
public int getItemCount() {
return mList.size();
}
#Override
public int getItemViewType(int position) {
return position == 0 ? RECYCLER_HEADER : RECYCLER_ITEMS;
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, final int i) {
int viewType=viewHolder.getItemViewType();
switch (viewType){
case RECYCLER_HEADER:
slideItemViewHolder = (SlideItemViewHolder) viewHolder;
slideItemViewHolder.updateHeader();
break;
case RECYCLER_ITEMS:
final RecyclerItemViewHolder holder = (RecyclerItemViewHolder) viewHolder;
final Restaurant restaurant = mList.get(i);
Picasso.with(mContext)
.load(restaurant.getVendorLogo())
.placeholder(R.mipmap.ic_launcher)
.error(R.mipmap.ic_launcher)
.into(holder.restaurentImageView);
holder.restaurentNameTextView.setText(restaurant.getName());
//Remaining code here
break;
default:
throw new RuntimeException(TAG+":Unable to bind the viewType"+viewType);
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
switch (viewType){
case RECYCLER_HEADER:
return new SlideItemViewHolder(inflater.inflate(R.layout.slide_show_restaurant_fragment_list,viewGroup,false));
case RECYCLER_ITEMS:
return new RecyclerItemViewHolder(inflater.inflate(R.layout.new_restautant_list_items, viewGroup, false));
default:
throw new RuntimeException(TAG+":Invalid ViewType "+viewType);
}
}
public static class RecyclerItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private ClickListener clickListener;
//Initialization here.
public RecyclerItemViewHolder(View v) {
super(v);
ButterKnife.inject(this, v);
v.setOnClickListener(this);
}
#Override
public void onClick(View v) {
clickListener.onClick(v, getPosition(), false);
}
public interface ClickListener {
/**
* Called when the view is clicked.
*
* #param v view that is clicked
* #param position of the clicked item
* #param isLongClick true if long click, false otherwise
*/
public void onClick(View v, int position, boolean isLongClick);
}
/* Setter for listener. */
public void setClickListener(ClickListener clickListener) {
this.clickListener = clickListener;
}
}
// id = 87,170
private class SlideItemViewHolder extends RecyclerView.ViewHolder {
SliderLayout sliderLayout;
LinearLayout rootLinearLayout;
public SlideItemViewHolder(View recyclerHeader) {
super(recyclerHeader);
sliderLayout = (SliderLayout) recyclerHeader.findViewById(R.id.home_slider);
rootLinearLayout = (LinearLayout) recyclerHeader.findViewById(R.id.rootLinearLayout);
}
private void updateHeader() {
if(Util.isNetworkAvailable(mContext)){
for (int i = 0; i < mData.size(); i++) {
DefaultSliderView defaultSliderView = new DefaultSliderView(mContext);
final int finalI = i;
defaultSliderView.image(mData.get(finalI).getImageUrl())
.setOnSliderClickListener(new BaseSliderView.OnSliderClickListener() {
#Override
public void onSliderClick(BaseSliderView slider) {
Restaurant restaurantById = Restaurant.searchByRestaurantId(mData.get(finalI).getTargetVendorId());
if(restaurantById != null)
openDetailFragment(restaurantById);
}
});
sliderLayout.addSlider(defaultSliderView);
}
}
}
}
public void openDetailFragment(Restaurant restaurant) {
Intent intent = new Intent(mContext, DetailTabActivity.class);
intent.putExtra(DetailTabActivity.INTENT_RESTAURANT_DATA, restaurant);
mContext.startActivity(intent);
}
public SliderLayout getSliderLayout(){
return slideItemViewHolder.sliderLayout;
}
}
And this adapter is set and updated from this fragment RestaurantFragment.java as:
private void setAdapter() {
dismissDialog();
if (getActivity() != null)
getActivity().runOnUiThread(new Runnable(){
#Override
public void run() {
if (restaurantList != null && restaurantList.size() > 0) {
restaurantRecyclerView.setVisibility(View.VISIBLE);
mEmptyListTextView.setVisibility(View.GONE);
restaurentListAdapter = new RestaurantAdapter(getActivity(), restaurantList, mRestaurantType);
restaurantRecyclerView.setAdapter(restaurentListAdapter);
restaurentListAdapter.updateAdapter(mData);
restaurantRecyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener(mLayoutManager) {
#Override
public void onLoadMore(int current_page) {
mCurrentPage = mCurrentPage + 1;
getHttpResturantData();
}
});
}
}
});
}
Is this much of an explanation helpful or should I paste more code?
Based on your code, it's possible to remove the header based on a certain condition.
Adjust your code to cater for the following:
#Override
public int getItemViewType(int position) {
if(hasHeaeder()) { // where you add the header
return position == 0 ? RECYCLER_HEADER : RECYCLER_ITEMS;
} else { // where you don't add the header
return RECYCLER_ITEMS;
}
}
This code also needs changing (currently it's wrong since it doesn't take care of the fact that the header adds 1 to the position).
final Restaurant restaurant = mList.get(i);
Replace it with
final Restaurant restaurant = hasHeader() ? mList.get(i +1) : mList.get(i);
Where hasHeader() is the code you need to write in order to determine whether or not the recycler should contain a header.

What is the SortedList<T> working with RecyclerView.Adapter?

Android Support Library 22.1 was released yesterday. Many new features were added into the v4 support library and v7, among which android.support.v7.util.SortedList<T> draws my attention.
It's said that, SortedList is a new data structure, works with RecyclerView.Adapter, maintains the item added/deleted/moved/changed animations provided by RecyclerView. It sounds like a List<T> in a ListView but seems more advanced and powerful.
So, what is the difference between SortedList<T> and List<T>? How could I use it efficiently? What's the enforcement of SortedList<T> over List<T> if it is so? Could somebody post some samples of it?
Any tips or codes will be appreciated. Thanks in advance.
SortedList handles the communication to the Recycler adapter via Callback.
One difference between SortedList and List is seen in the addAll helper method in the sample below.
public void addAll(List<Page> items) {
mPages.beginBatchedUpdates();
for (Page item : items) {
mPages.add(item);
}
mPages.endBatchedUpdates();
}
Keeps last added item
Say I have 10 cached items to load immediately when my recycler list is populated. At the same time, I query my network for the same 10 items because they could have changed since I cached them. I can call the same addAll method and SortedList will replace the cachedItems with fetchedItems under the hood (always keeps the last added item).
// After creating adapter
myAdapter.addAll(cachedItems)
// Network callback
myAdapter.addAll(fetchedItems)
In a regular List, I would have duplicates of all my items (list size of 20). With SortedList its replaces items that are the same using the Callback's areItemsTheSame.
Its smart about when to update the Views
When the fetchedItems are added, onChange will only be called if one or more of the Page's title changed. You can customize what SortedList looks for in the Callback's areContentsTheSame.
Its performant
If you are going to add multiple items to a SortedList, BatchedCallback call convert individual onInserted(index, 1) calls into one onInserted(index, N) if items are added into consecutive indices. This change can help RecyclerView resolve changes much more easily.
Sample
You can have a getter on your adapter for your SortedList, but I just decided to add helper methods to my adapter.
Adapter Class:
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private SortedList<Page> mPages;
public MyAdapter() {
mPages = new SortedList<Page>(Page.class, new SortedList.Callback<Page>() {
#Override
public int compare(Page o1, Page o2) {
return o1.getTitle().compareTo(o2.getTitle());
}
#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);
}
#Override
public boolean areContentsTheSame(Page oldItem, Page newItem) {
// return whether the items' visual representations are the same or not.
return oldItem.getTitle().equals(newItem.getTitle());
}
#Override
public boolean areItemsTheSame(Page item1, Page item2) {
return item1.getId() == item2.getId();
}
});
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.viewholder_page, parent, false);
return new PageViewHolder(view);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
PageViewHolder pageViewHolder = (PageViewHolder) holder;
Page page = mPages.get(position);
pageViewHolder.textView.setText(page.getTitle());
}
#Override
public int getItemCount() {
return mPages.size();
}
// region PageList Helpers
public Page get(int position) {
return mPages.get(position);
}
public int add(Page item) {
return mPages.add(item);
}
public int indexOf(Page item) {
return mPages.indexOf(item);
}
public void updateItemAt(int index, Page item) {
mPages.updateItemAt(index, item);
}
public void addAll(List<Page> items) {
mPages.beginBatchedUpdates();
for (Page item : items) {
mPages.add(item);
}
mPages.endBatchedUpdates();
}
public void addAll(Page[] items) {
addAll(Arrays.asList(items));
}
public boolean remove(Page item) {
return mPages.remove(item);
}
public Page removeItemAt(int index) {
return mPages.removeItemAt(index);
}
public void clear() {
mPages.beginBatchedUpdates();
//remove items at end, to avoid unnecessary array shifting
while (mPages.size() > 0) {
mPages.removeItemAt(mPages.size() - 1);
}
mPages.endBatchedUpdates();
}
}
Page class:
public class Page {
private String title;
private long id;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
}
Viewholder xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/text_view"
style="#style/TextStyle.Primary.SingleLine"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Viewholder class:
public class PageViewHolder extends RecyclerView.ViewHolder {
public TextView textView;
public PageViewHolder(View itemView) {
super(itemView);
textView = (TextView)item.findViewById(R.id.text_view);
}
}
SortedList is in v7 support library.
A SortedList implementation that can keep items in order and also
notify for changes in the list such that it can be bound to a
RecyclerView.Adapter.
It keeps items ordered using the compare(Object, Object) method and
uses binary search to retrieve items. If the sorting criteria of your
items may change, make sure you call appropriate methods while editing
them to avoid data inconsistencies.
You can control the order of items and change notifications via the
SortedList.Callback parameter.
Here below is a sample of use of SortedList, I think it is what you want, take a look at it and enjoy!
public class SortedListActivity extends ActionBarActivity {
private RecyclerView mRecyclerView;
private LinearLayoutManager mLinearLayoutManager;
private SortedListAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sorted_list_activity);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mRecyclerView.setHasFixedSize(true);
mLinearLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLinearLayoutManager);
mAdapter = new SortedListAdapter(getLayoutInflater(),
new Item("buy milk"), new Item("wash the car"),
new Item("wash the dishes"));
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setHasFixedSize(true);
final EditText newItemTextView = (EditText) findViewById(R.id.new_item_text_view);
newItemTextView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
if (id == EditorInfo.IME_ACTION_DONE &&
(keyEvent == null || keyEvent.getAction() == KeyEvent.ACTION_DOWN)) {
final String text = textView.getText().toString().trim();
if (text.length() > 0) {
mAdapter.addItem(new Item(text));
}
textView.setText("");
return true;
}
return false;
}
});
}
private static class SortedListAdapter extends RecyclerView.Adapter<TodoViewHolder> {
SortedList<Item> mData;
final LayoutInflater mLayoutInflater;
public SortedListAdapter(LayoutInflater layoutInflater, Item... items) {
mLayoutInflater = layoutInflater;
mData = new SortedList<Item>(Item.class, new SortedListAdapterCallback<Item>(this) {
#Override
public int compare(Item t0, Item t1) {
if (t0.mIsDone != t1.mIsDone) {
return t0.mIsDone ? 1 : -1;
}
int txtComp = t0.mText.compareTo(t1.mText);
if (txtComp != 0) {
return txtComp;
}
if (t0.id < t1.id) {
return -1;
} else if (t0.id > t1.id) {
return 1;
}
return 0;
}
#Override
public boolean areContentsTheSame(Item oldItem,
Item newItem) {
return oldItem.mText.equals(newItem.mText);
}
#Override
public boolean areItemsTheSame(Item item1, Item item2) {
return item1.id == item2.id;
}
});
for (Item item : items) {
mData.add(item);
}
}
public void addItem(Item item) {
mData.add(item);
}
#Override
public TodoViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
return new TodoViewHolder (
mLayoutInflater.inflate(R.layout.sorted_list_item_view, parent, false)) {
#Override
void onDoneChanged(boolean isDone) {
int adapterPosition = getAdapterPosition();
if (adapterPosition == RecyclerView.NO_POSITION) {
return;
}
mBoundItem.mIsDone = isDone;
mData.recalculatePositionOfItemAt(adapterPosition);
}
};
}
#Override
public void onBindViewHolder(TodoViewHolder holder, int position) {
holder.bindTo(mData.get(position));
}
#Override
public int getItemCount() {
return mData.size();
}
}
abstract private static class TodoViewHolder extends RecyclerView.ViewHolder {
final CheckBox mCheckBox;
Item mBoundItem;
public TodoViewHolder(View itemView) {
super(itemView);
mCheckBox = (CheckBox) itemView;
mCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (mBoundItem != null && isChecked != mBoundItem.mIsDone) {
onDoneChanged(isChecked);
}
}
});
}
public void bindTo(Item item) {
mBoundItem = item;
mCheckBox.setText(item.mText);
mCheckBox.setChecked(item.mIsDone);
}
abstract void onDoneChanged(boolean isChecked);
}
private static class Item {
String mText;
boolean mIsDone = false;
final public int id;
private static int idCounter = 0;
public Item(String text) {
id = idCounter ++;
this.mText = text;
}
}
}
There is a sample SortedListActivity in the support library source repository which demonstrates how to use SortedList and SortedListAdapterCallback inside a RecyclerView.Adapter. From the root of the SDK, with the support library installed, it should be at extras/android/support/samples/Support7Demos/src/com/example/android/supportv7/util/SortedListActivity.java (also on github).
The existence of these particular samples is mentioned exactly once in Google's documentation, at the bottom of a page dealing with a different topic, so I don't blame you for not finding it.
About SortedList implementation, it is backed by an array of <T> with a default min capacity of 10 items. Once the array is full the array is resized to size() + 10
The source code is available here
From documentation
A Sorted list implementation that can keep items in order and also
notify for changes in the list such that it can be bound to a
RecyclerView.Adapter.
It keeps items ordered using the compare(Object, Object) method and
uses binary search to retrieve items. If the sorting criteria of your
items may change, make sure you call appropriate methods while editing
them to avoid data inconsistencies.
You can control the order of items and change notifications via the
SortedList.Callback parameter.
Regarding to performance they also added SortedList.BatchedCallback to carry out multiple operation at once instead of one at the time
A callback implementation that can batch notify events dispatched by
the SortedList.
This class can be useful if you want to do multiple operations on a
SortedList but don't want to dispatch each event one by one, which may
result in a performance issue.
For example, if you are going to add multiple items to a SortedList,
BatchedCallback call convert individual onInserted(index, 1) calls
into one onInserted(index, N) if items are added into consecutive
indices. This change can help RecyclerView resolve changes much more
easily.
If consecutive changes in the SortedList are not suitable for
batching, BatchingCallback dispatches them as soon as such case is
detected. After your edits on the SortedList is complete, you must
always call dispatchLastEvent() to flush all changes to the Callback.

Categories

Resources