Skip to content

Instantly share code, notes, and snippets.

@cutiko
Last active November 3, 2021 08:34
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save cutiko/f2d4b921e2ae2d362329b693cd6e219c to your computer and use it in GitHub Desktop.
Save cutiko/f2d4b921e2ae2d362329b693cd6e219c to your computer and use it in GitHub Desktop.
How to work with adapters, RecyclerView and ListView

Overview

This snippet is focus on how to create a RecyclerAdapter, for so please refer to the files:

  1. SomeFragment.java
  2. CustomRecyclerAdapter.java

There you will find details. In case you need you can see the classic ListView adapter in the file ListViewAdapter.java by example if you need to create a dropdown a.k.a AndroidSpinner. If you are looking to create an Android spinner then this tutorial should help you.

Instructions

Learning how to use the adapter for the RecyclerView is very important, is an optimized adapter in comparison to the previous for the ListView. This gist assume you have an xml layout for your row, if you haven't create it yet, do it now

  1. Go to the CustomRecyclerAdapter.java and adapt what you need.
  2. Then you have to set the adapter to the RecyclerView, you can see how to do that in the SomeFragment.java file

Please pay close attention to what is marked as important inside the CustomRecyclerAdapter, it will spare you some headaches.

Step by step

  1. The layout of your Activity or Fragment has to have 1 RecyclerView
  2. Create your model
  3. Create a list_item_[model]
  4. Think a way in wich you will get the data (a field inside the adapter, the adapter constructor, etc)
  5. Create a class that will be the adapter [YourModels]Adapter, but don't extend it to the adapter yet
  6. Inside your new class create an inner class name it [YourModel]Holder, it has to extends RecyclerView.ViewHolder
  7. The view holder created is the Java representation of the layout row in the step 2, add the constructor matching super
  8. Inside the holder create one variable for each view you will need to set data in the layout
  9. Initialilze each view inside the constructor mostly like this foo = itemView.findViewById(R.id.bar)
  10. Now go back to the adapter class you create and extend it RecyclerView.Adapter please read the next step
  11. The RecyclerView.Adapter takes 1 param between angles brackes, your view holder RecyclerView.Adapter<YourModelHolder>
  12. Now the class it will lint an error, press alt+enter on the red lining and select implement methods
  13. You should now have 3 methods, onBindViewHolder, onCreateHolder and getItemCount
  14. In the step 3 you design how the data is gonna be pass to the adapter, now is the time to make it happen
  15. Once you have solved that, got to getItemCount and make it return to the size of your list.
  16. Please look at the adapter below and make onCreateHolder does what it has to do, is always the same
  17. Then go to onBindViewHolder and set the data in the row (the holder)
  18. Next step is handling clicks
  19. Create an interface add there 1 method that has to have as arguments the param you need to pass when the row is clicked
  20. Make a field inside the adapter that is your interface private ClickListener listener
  21. Add that field in the constructor
  22. Wherever you are instancing the adapter it has to implements the interface YourActivity extends AppCompatActivity implements YourInterface
  23. Find the RecyclerView
  24. Set a LayoutManager to the RecyclerView commonly LinearLayoutManager (is not LinearLayout)
  25. Instance the adapter by example YourModelsAdapter adapter = new YourModelsAdapter(this), this is the implemented interface
  26. Set the adapter to the RecyclerView
  27. Add this gist to your favorites

Common errors

  1. Layout of the Activity or Fragment is wrong
  2. Layout of the row list_item_[model] is wrong, is usually width match_parent and height wrap_content
  3. You haven't set the LayoutManager
  4. getItemCount is wrong
  5. You haven't implement the interface
  6. The adapter data doesn't exist
//Create the inner class before extending it, yes obvious, but sometimes can be forgotten
public class CustomRecyclerAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> {
//Sometimes you can have your set of data as empty but initialized and then update it using an update method
//This is usefull when you want to do an http request, set the adapter empty then when the AsyncTask is done, refresh it
private List<YourObject> mObjects = new ArrayList();
//There is no constructor matching the super here, so create your own with what ever you need
public CustomAdapter() {
}
//Maybe you need just to pass the id, then make the query inside the constructor
public CustomAdapter(long id) {
mObjects = YourObject.someQuery(id);
}
//You can pass the data from the View
public CustomAdapter(List<YourObject> mObjects) {
this.mObjects = mObjects;
}
//This will inflate your view
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//The RecyclerView is very interesting because it will literrally recycle the view, if you put a log here, and in onBindViewHolder
//you will notice this only happen some times. RecyclerView create an optimized pool of view, if the view is available there
//this will not happen then onBindViewHolder will set the data to the view directly
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_custom, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
YourObject object = mObjects.get(position);
//This is the view you set in the subclass
holder.name.setText(object.getName());
//Lets change the text color on click
holder.name.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/*
VERY IMPORTANT
*/
//Your data (object) neither the position (in the method argument) can't be final
//final data or position will create inconsistencies in the list
//Since the view is recycled, if the data or position is final it will not be redefined when
//making some rows have data belonging to other rows
//The correct way to access data inside an inner implementation of an interface such as a click listener
//is not strugling with the recycler logic, but using on your advantage
//You have to use the holder and get the position from there
YourObject auxObect = mObjects.get(holder.getAdapterPosition());
if (auxObject.isColored()) {
v.setTextColor(ContextCompat(v.getContext(), getColor(R.color.colorAccent))
} else {
v.setTextColor(ContextCompat(v.getContext(), getColor(android.R.color.black))
}
}
});
//You don't need the context, you can get it from any view
ImageView imageView = holder.photo;
Picasso.with(imageView.getContext()).load(object.getUrl()).into(imageView);
}
//If you are starting with an empty list, you migth want to have some method like this
public void update(List<YourObject> objects) {
mObjects.addAll(objects);
notifyDataSetChanged();
}
/*
VERY IMPORTANT
*/
//Always start with this method, if return 0; no mather everything else is fine, there will be nothing in the view
@Override
public int getItemCount() {
return mObject.size();
}
//This inner class have to be created first, so the parent class can extend to it
//A ViewHolder is the representation of your xml layout R.layout.list_item_custom
public static class ViewHolder extends RecyclerView.ViewHolder {
//Every variable here should be the representation of each xml element in the layout of the row
private TextView name;
private ImageView photo;
ViewHolder(View view) {
super(view);
//You can find yout views because the argument view is the inflated layout in the onCreateViewHolder
name = (TextView) view.findViewById(R.id.itemNameId);
photo = (ImageView) view.findViewById(R.id.itemPhotoId);
}
}
}
//Dont forget to set the adapter to the RecyclerView and mainly, RecyclerViews need a LayoutManager so you need to do that also
//Here Im passing a custom model, but you can use a String
public class CustomAdapter extends ArrayAdapter<YourObject> {
//Most of the time this is your constructor, otherwise maybe you need the YourObject[]
public AssignationAdapter(Context context, int resource, List<c> objects) {
super(context, resource, objects);
//If you create a global variable int resource, and here this.resource = resource, you can change the resource programatically by the instatiation
}
//You are going to use this subclass as a way to access your views as variables
private class ViewChildHolder {
//Make sure you have 1 variable for each view you want to manipulate, this view could be a LinearLayout with a TV and a IV
TextView objectName;
ImageView objectPhoto;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
ViewChildHolder holder;
//This will inflate your view using the subclass above
if (convertView == null) {
//You are passing your resource again here, cause there is no resource global variable
convertView = inflater.inflate(R.layout.list_item_custom, parent, false);
holder = new ViewChildHolder();
//Here you can find the views you want to manipulate
holder.objectName = (TextView) convertView.findViewById(R.id.objectNameId);
//TODO find the IV holder holder.objectPhoto = ...
convertView.setTag(holder);
} else {
holder = (ViewChildHolder) convertView.getTag();
}
//get the item by posisiont and set the name to the TV
holder.objectName.setText(getItem(position).getName());
return convertView;
}
/*If you are working with a spinner then the this method will change the dropdown elements appearance. The previous
getView method will be the view in the spinner. You can do here the same that is done in getView*/
/*@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return convertView;
}*/
@Override
public long getItemId(int position) {
//Does your object has an Id of some sort, then override this and you will get the Id in the itemClickListener by example
return getItem(position).getid();
}
}
//Dont forget to set the adapter later to your view
public class SomeFragment {
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//The xml is a RecyclerView
RecyclerView recyclerView = (RecyclerView) view;
/*Layout Managers Example*/
//Linear Vertical by default
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
//Linear but horizontal (the third boolean param can invert the order, the second can be Vertical)
LinearLayoutManager horizontalManager = new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false);
//Grid
GridLayoutManager gridLayoutManager = new GridLayoutManager(context, 2);
//You can also set it to the the Recycler anonymously
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
//Calculating the size of the row is a heavy operation, if you know all the rows have the same size set this to true, for better performance
recyclerView.setHasFixedSize(true);
//If you dont set the adapter then nothing will be shown
SomeAdapter adapter = new SomeAdapter();
recyclerView.setAdapter(adapter);
}
}
@cutiko
Copy link
Author

cutiko commented Aug 5, 2016

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment