Hey, looking at the multiple peaces of code here, I cannot wrap my head around how RecyclerView is choosing the right viewtype for that crime after they have been created. I understand how it’s choosing one when first creating one as it checks using getItemViewType.
For example, if I have every fifth crime as a serious crime which has a different view with a button in it, after 2-3 have been made, how is it able to use those again. If there was one view type then I would understand as it is using the same CrimeHolder going off screen and reusing it, but with the code for the challenge with 2 viewTypes, how is it able to deduce which viewType it has to use.
I see that getItemViewType seems to only be used in the onCreateViewHolder and not in the onBindViewHolder. How is onBindViewHolder calling the right bind method and not the abstract class’ bind method as seen there.
I’m really bad at explaining this, hope you understand even a bit of it. Thank you
Hi Fr0zen1,
Got your point.
After the initial creations of the ViewHolders with onCreateViewHolder(), in a common way, only onBindViewHolder() should be called to update the created ViewHolders.
I put a debug log in both onCreateViewHolder() and onBindViewHolder() methods.
However, according to the test result, when I changed the choice of ViewType in the app (in later chapters, it can be configured in CrimeFragment.java), I found the onCreateViewHolder() was called again.
I have a suspect if the ViewType of the created ViewHolder has been changed, the onCreateViewHolder() will be called again. However, it is not confirmed by source code yet.
I will try to check the caller of the onCreateViewHolder() again.
Please also share the infos if you find new things.
Thanks
BR/CrystalDf
1 Like
Yes I’ll be testing further tomorrow. I understand how it’s calling the right bind function. Put in C++: It’s like calling a member function on a pointer on a superclass and then at runtime it deduces what function to call depending on the actual type the pointer is pointing at. I understand java doesn’t have pointers and is different but it’s easier to understand coming from C++
I still don’t understand however how it’s choosing the viewType in the first place for calling the bind function on.
For me only 3 seriousCrimeHolders are created and around 10 of regularcrimeholders. And they’re being used over and over again correctly without further ones being created.
- I think you mean the term Polymorphism in java.
- I don’t think the task of checking the viewType is done by onBindViewHolder().
When onBindViewHolder() is called, it means the viewHolder with the same viewType can be reused. However, if the viewType is changed, the viewHolder will be recycled and the onCreateViewHolder() will be called again.
I don’t find the exact source code but only the following one in RecyclerView.java.
I guess it first check whether the viewType is same as the old viewHolder’s. If yes, the same viewHolder will be returned. Otherwise, the viewHolder will be recycled.
ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
// ......
final int cacheSize = mCachedViews.size();
for (int i = cacheSize - 1; i >= 0; i--) {
final ViewHolder holder = mCachedViews.get(i);
if (holder.getItemId() == id) {
if (type == holder.getItemViewType()) {
if (!dryRun) {
mCachedViews.remove(i);
}
return holder;
} else if (!dryRun) {
recycleCachedViewAt(i);
return null;
}
}
}
return null;
}
/**
* Recycles a cached view and removes the view from the list. Views are added to cache
* if and only if they are recyclable, so this method does not check it again.
*/
void recycleCachedViewAt(int cachedViewIndex) {
if (DEBUG) {
Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
}
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
if (DEBUG) {
Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
}
addViewHolderToRecycledViewPool(viewHolder, true);
mCachedViews.remove(cachedViewIndex);
}
1 Like
Here is the code in CrimeListFragment.java. Just make sure we are on the same stage.
Different objects (SeriousCrimeHolder object or RegularCrimeHolder object) will be created depending on the value of viewType. i.e. When onBindViewHolder() is called, the caller/viewHolder will use the correct way to finish the bind task.
@Override
public int getItemViewType(int position) {
Crime crime = mCrimes.get(position);
return crime.isRequiresPolice() ? REQUIRES_POLICE : NOT_REQUIRES_POLICE;
// The isRequiresPolice() method helps toggle the attribute dynamically. It will be introduced in later chapters.
}
@Override
public CrimeHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
switch (viewType) {
case REQUIRES_POLICE:
return new SeriousCrimeHolder(layoutInflater, parent);
case NOT_REQUIRES_POLICE:
return new RegularCrimeHolder(layoutInflater, parent);
default:
return null;
}
}
@Override
public void onBindViewHolder(CrimeHolder holder, int position) {
Crime crime = mCrimes.get(position);
holder.bindCrime(crime);
}
private abstract class CrimeHolder extends RecyclerView.ViewHolder {
//......
public CrimeHolder(LayoutInflater inflater, ViewGroup container, @LayoutRes int resource) {
super(inflater.inflate(resource, container, false));
// ......
}
//......
}
private class RegularCrimeHolder extends CrimeHolder {
public RegularCrimeHolder(LayoutInflater inflater, ViewGroup container) {
super(inflater, container, R.layout.list_item_crime);
}
}
private class SeriousCrimeHolder extends CrimeHolder {
//......
public SeriousCrimeHolder(LayoutInflater inflater, ViewGroup container) {
super(inflater, container, R.layout.list_item_crime_requires_police);
//......
}
}
2 Likes
Yes polymorphism is what I meant.
I think I’ve wrapped my head around most of this as it seems to be calling getItemViewType on the actual holder which I presume the two subclasses return a different value for? Just trying to figure out how exactly it’s differentiating between the two views when we pass a parent class.
Yes that is the code I have as well.
Thank you for your help, it’s cleared a lot up for me
Okay I finally understand it. Thank you crystalDf!
So I’m just going to reiterate what I believe is happening. Please let me know if I’ve misunderstood something.
So firstly the two different views are created using RecyclerView.Adapter<>:getItemViewType
which we have implemented and have use in the onCreateViewHolder
. That bit is straight forward.
The second part is very similar. Behind the scenes, any views going off screen are recycled. This means the view holders are not destroyed but rather assigned data from another Crime
object. RecyclerView goes through all its cached views(the ones gone off screen) and calls the getItemViewType
on the actual holder. This method we have not implemented but rather returns an index, every unique value being assigned to each type of holder. This way, SeriousCrimeHolder and RegularCrimeHolder have different index values which are checked by the RecyclerView when it is choosing whether a view should be recycled or a new one should be created.
Thanks for all your help.
My understanding of the whole procedure is as follows.
I scan the source code of RecyclerView.java and skip many conditional steps.
-
No viewHolder is created or cached/recycled at first.
-
When RecyclerView needs to show a new crime, getItemViewType() will be called and then you know the viewType for this crime.
-
Since no cached/recycled viewHolder for the specified viewType is available, onCreateViewHolder() will be called. An instance of SeriousCrimeHolder or RegularCrimeHolder will be created depending on the viewType.
-
onBindViewHolder() will be called for the specified viewHolder to bind the related crime.
-
After several times of 2-4, you will have two viewHolder cached/recycled lists. One is for SeriousCrimeHolder(viewType==I) and the other is for RegularCrimeHolder(viewType==II).
-
When RecyclerView needs to show a new crime, getItemViewType() will be called and then you know the viewType for this crime.
-
Now we are at the fork in a road.
a. If the viewHolder for the specified viewType is available in a cached/recycled list, it will be returned and reused. i.e. onCreateViewHolder() will not be called in this case.
b. If the viewHolder for the specified viewType is not available in a cached/recycled list, null will be returned and onCreateViewHolder() will be called. After that, a new viewHolder is created.
-
onBindViewHolder() will be called for the specified viewHolder to bind the related crime.
// in RecyclerView.java
if (holder == null) {
//......
holder = mAdapter.createViewHolder(RecyclerView.this, type);
//......
}
1 Like
Yup that seems to be exactly what’s happening. Thank you for clearing it up!