RecyclerView recreating ViewHolder instead of rebinding - android

Basically I have a RecyclerView with chat messages. The scenario is as follows:
I add a single message -> Causes RecyclerView to add a new ViewHolder
Delay of about 2 seconds
I change the first message and add a new one -> Should cause RecyclerView to rebind the first message and create a new ViewHolder for the second one.
My problem lies in step 3. as the first ViewHolder is removed and a new one is created and then bound. This produces a very slight flickering which is a little bit annoying and all in all this shouldn't happen.
I am using DiffUtil to dispatch changes, the whole setup is a little bit more involved so it is hard to describe the whole picture but I narrowed it down to this:
The DiffUtil produces the correct result and calls onItemRangeChanged for the first item and onItemRangeInserted for the second item. For whatever reason the first ViewHolder is then removed and a new on is created, added and then bound.
Did somebody experienced similar behaviour or has a clue why this could be the case? I tried to debug through the RecyclerView code but it is a nightmare and I still don't understand the whole thing. What could be the reason that the first ViewHolder cannot be rebound and a new one has to be created?
If I can provide any more information just ask for it and I will try my best.
A small repository showing the problem can be found here: https://github.com/MaxGierlachowski/recyclerview_viewholder_bug
If you look into the error logs and look at the 4th "MESSAGE" log you see that a onChanged and a onInserted are dispatched to the adapter but there is a new ViewHolder created for the onChanged one. I know that RecyclerView checks for a lot of things like animation completion and so on but I really would like to know why the ViewHolder isn't rebound but recreated.
EDIT:
Something I figured out was that if that createViewHolder is called by the RecyclerView the ViewHolder that should be reused is inside the mChangedScrap list and mState.isPreLayout() is false so the function tryGetViewHolderForPositionByDeadline() doesn't search the mChangedScrap list and creates a new one. Still this shouldn't happen, the ViewHolder isn't even animated or something and it seems like all animations are finished at that point.
Small video of the flickering:

Hey so I figured it out myself but I still don't fully understand why this is necessary. So the ItemAnimator inside RecyclerView decides whether a changed ViewHolder will be just rebound or recreated. It will decide it on three factors that are mentioned in the article below. So basically because I was using the DiffUtil I just had to provide some arbitrary to the onItemRangeChanged call and it would reuse the ViewHolder instead of creating a new one.
It was this article that help me understand the problem:
https://medium.com/android-news/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-continued-d81c631a2b91

Probably you have a problem with your DiffUtil.Callback. From the documentation, there is a method areItemsTheSame(T oldItem, T newItem) based on the result diff util could remove and add new holder or just update the content of existing holder. Make sure that your implementation is right.
areItemsTheSame -> check for example that left and right holders are the same types of holders.
areContentsTheSame -> here you should check for equality. object1.equals(object2)

Related

Android FastAdapter ClickEventHook isn't working properly

I am seeking forgiveness for this simple problem. I am using FastAdapter for the first time and I am new to Kotlin.
Let me share the problem.
I have a layout for the recyclerview item holder, an adapter which extends AbstractItem class.
Actually I couldn't format the code correctly here on Stack overflow. So I created the code at GitHub Gist. Here is the link.
In the layout_add_item.xml file, I have two buttons, one's id is addStudent and another one's id is addTeacher.
So, my problem is, these two button's onClickListener is not triggered until I click on the root layout once on every onCreate method is called. If I click on the LinearLayout once, then these two button's onClickEvent get fired normally.
I don't know what's wrong here :-(
Any help would be appreciated. Thanks in advance.
The concept behind the EventHook system exposed in the FastAdapter is meant to ensure listeners are not loosely re-attached everytime the data gets bound to the item, or even worse to introduce memory leaks.
The API itself due to that tries to prevent those various aspects.
Using Kotlin the FastAdapter exposes a method which should simplify this setup procedure some more for you.
For example:
mFastAdapter.addClickListener({ vh: SimpleImageItem.ViewHolder -> vh.imageView }) { view, position, fastAdapter, item ->
// react on the click on the `imageView` of the `SimpleImageItem.ViewHolder`
}

How to reuse activity components in a viewHolder

Initially I had an activity which shows some data after an api call. Now I need to convert this activity to a recyclerView.
The activity also had some async calls, means after the very first call it takes the data and call the second api and then update the ui.
My project is in mvp architecture. I wanted to know what component can I reuse in my view holder so I can achieve a list similar to my activiy with minimum code. Because copy pasting all the click interactions and api call is of too much pain.
I can provide code samples if needed. Currently I took a list of elements in the beginning for my recyclerview and started redoing the whole work.
Make your methods public which you want to reuse and call them like this
((Activity)holder.itemView.getContext()).method();

Android ViewModel inside RecyclerView Adapter for lazy database downloads

I have a question that is more related to proper design and architecture of MVVM than to coding itself. In my project I have a situation that on ViewModel is suplying data, that are later used in RecyclerView.Adapter to create a proper view.
However I wonder if this would be also correct (from proper 'way of doing things' POV) if some of the data would be supplied in form of id's to be further fetched from Room or external server? For instance during onBindViewHolder use some LiveData with observe() to update certain fields on succesfull load.
Doing data fetch in the views is a no-go. It defeats the very purpose of MVVM and in particular the Android Jetpack efforts. One of the big reason is the data needs to survive configurations. Putting "data fetching" mechanism in the view defeats that as the view can be destroyed and recreated anytime when need be.
So I would suggest you make sure all calls to the network or any other source of data for that matter revolve around the ViewModel and never a view. Let the VM feed the data to the View through observer.
Exception to what I have just said is such use case as loading images with Picasso or Glide where by you feed them URL and they load image. But that's a different thing as they are designed to handle that.
UPDATE with followup Questions
it's ok to put observe() still inside Adapter, during the binding process?
No! Data sent to the adapter must be complete in the purpose it is supposed to serve. For example, if you have to do list app and your Top-Level Activity displays all Todos, then you must feed adapter will complete data (Title, Created time, et al).
The data that are not going to be displayed (like descriptions or sub-to-do-lists) and aren't necessary to identify specific to do should not be fetched (unless you want to store them for sole purpose of avoiding a second network call and pass them to the next activity as serialized data).
When user clicks specific To-Do, then launch new activity with its own ViewModel that will fetch details for that activity (assuming you passed some ID with intent).
If the first, then I understand that observe() should not only update data, but also populate it later to Observer and call notifyDataSetChanged(), right?
Observe is a way to post data to the view when either it have changed or the old view was destroyed and so the new view is being given the same old data that survived the "view death". So it is in the same method where updating data of the Adapter should be done and hence call to notifyDataSetChanged() or similar methods.
I hope that makes it clear.
I think it's best to keep the ViewModel separate from the Adapter. From what I'm gathering you want to basically have each list item load it's own data from Room by having each list item observe on a particular ID for that item. This sounds rather inefficient as you're basically having each item execute a query on the database (or network call) to retrieve just one item for all items that are going to be displayed, think about how it will scale if you were displaying a 100 items. I think it's best that you just execute one query to get the list of data and set the data to the list items in the adapter, keep it simple. Note that onBindViewHolder() doesn't just get called once but multiple times when you're scrolling the screen, so it could get quite ugly if you're trying to lazily load every list item.

What is the point of using realm-android-adapter?

There is no mention of why. I still have to code all the view holder stuff, so what is the benefit of using it instead of RecyclerView.Adapter?
Just curious.
It automatically adds a RealmChangeListener to your RealmResults, thus automatically calling adapter.notifyDataSetChanged() when the underlying elements are written to. Therefore you don't need to write the code to keep your adapter in sync with the results.

Multiple instances of custom View with onClickListeners, general feedback

I've got an activity with several instances of a custom View. This custom View is passed an object, and the contents of this object will decide how the View is displayed.
Also, when the View is created, an OnClickListener is added to it. The previously mentioned object is passed on to this onClickListener through a contructor and private variables, and inside the listener I've got a switch statement which will handle the OnClick differently depending on the object-data.
Is there anything fundamentally wrong with my approach? Would it be better to have several OnClickListeners, and add the correct one from the View, instead of evaluating the object inside the OnClickListener, thus reducing the size/memory footprint? I'm guessing all the onClickListeners will be loaded as objects in memory once the activity loads?
Maybe I should access the object and context through the view that is passed to onClick instead of passing them as arguments (and duplicating them?), however I'm struggling with acheiving this...
I'm basically looking to optimize CPU and memory usage. The program runs fine as is.
You may check this out:
How to test the performance of an Android application?
For memory:
Try to avoid (lots of) "new ..." calls. The more u create the more the GarbageCollector has to clean up afterwards. This create/cleanup needs a lot of performance.

Categories

Resources