When to use executePendingBindings() and when its not required? - android

I am looking for a practical example for both the cases that what to use when? I have seen similar threads but they only tell this "when binding must be executed immediately" but there is no real time example for any case where you have to force the binding to be executed. So please if anyone can explain with any example that when to use it and when its not that required!

The case when you should use executePendingBindings() is (but there could be more):
Every time the binding update could cause a View to change its size and postponing the calculation in the next frame could cause the measurement to read wrong values.
In case of a RecyclerView, this happens if:
You have multiple viewTypes with different sizes (different XML)
Your row height changes based on view contents.
RecyclerView will measure the row size after the onBindViewHolder has completed. If based on the data you set in this method the height of the row changes, the measurement could not take into account the extra or reduced space occupied by your new content if you do not call executePendingBindings().
Forcing the binding to update data synchronously and not in the next frame keeps you safe from wrong row size measurement (and potentially missing content)

Related

How do I know how many items the RecyclerView is going to inflate?

I have a RecyclerView.Adapter together with a list of items. At some point I will make some changes to this list and then I will call notifyDataSetChanged() (or another similar method). How do I know how many ViewHolders will be created prior to making this call? Or how many times onBindViewHolder will be called? Ideally I should know one of these numbers before calling this notify method, but I could also manage with receiving a callback when the adapter finishes to make all the work (inflations, bindings). From my experiments, the adapter is not done with the work right after calling notifyDataSetChanged.
I need to know how many items will be or were inflated. Ideally 'will be'. Some ideas on how I can achieve this?
UPDATE
Stumbled upon: Is there a callback for when RecyclerView has finished showing its items after I've set it with an adapter?, which gave me an answer to the second thing I asked: "I could also manage with receiving a callback when the adapter finishes to make all the work (inflations, bindings)."
I made some modifications to the accepted answer and the following Log only displays after all the recycler's items were inflated (at least in my app):
RecyclerView(context).apply {
layoutManager = object: LinearLayoutManager(context, VERTICAL, false) {
override fun onLayoutCompleted(state: RecyclerView.State?) {
super.onLayoutCompleted(state)
val firstVisibleItemPosition = findFirstVisibleItemPosition()
val lastVisibleItemPosition = findLastVisibleItemPosition()
if(firstVisibleItemPosition == -1 || lastVisibleItemPosition == -1)
return
Log.d("RECYCLER", "all sync inflations done")
}
}
}
How do I know how many ViewHolders will be created prior to making this call? Or how many times onBindViewHolder will be called?
Sorry, there is no way to know that. It will depend on all sorts of variables which are not known until rendering is being done.
For example, suppose that your RecyclerView is a vertically-scrolling list of rows, and each row shows some text in a TextView. How many rows will be needed will depend on:
What the text is, which could be short or long
What the row layout is and how it calls for that text to be rendered (font, font scale, padding, margin, etc.)
What the font scale is
What the RecyclerView size is (which in turn varies based on things like window size, which in turn varies based on things like screen size and whether we are in split-screen or multi-window mode, etc.)
And, in terms of how many ViewHolder objects will be instantiated, that will also depend on whether this is the first time the RecyclerView has been shown or whether you are updating an existing one with new data.
And that is just for very simple rows. More complex rows would add more dependencies.
Ideally I should know one of these numbers before calling this notify method
That is unrealistic. My guess it that is unnecessary. You might consider asking a separate Stack Overflow question where you explain why you feel that you need this, and perhaps we can suggest alternative approaches to solving your perceived problem. IOW, your question feels like an XY problem, where you have guessed at a solution to some other problem.

Does Android batch UI operations or re-draw every single command?

This question came to my mind when re-binding data to views in RecycleView. Before selectively applying UI changes to the views, I usually reset them all to their default states.
Such as
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
/* Reseting to normal/default state */
holder.title.visibility = View.VISIBLE
holder.poster.visibility = View.VISIBLE
/* Applying data */
if (data.poster.url == null) {
holder.poster.visibility = View.GONE
}
}
Here the poster View has potentially its visibility changed to VISIBLE and GONE again within a very short time interval.
Does Android actually invalidate and request drawing for both visibility changes? For example, if before binding, holder.poster view was GONE, then Android would draw it visible and then gone again?
Or does it batch them and only execute the latest state? For example, if we want the app to run at 60fps, it might batch UI operations in 16ms intervals. I can change Visibility hundreds of times, but it will actually draw the very last state within that 16ms batch.
The 2nd point holds true, but for a much more simple reason in your use-case.
The onBindViewHolder method runs on the main thread. Thus no other operation is possible on that thread until it returns.
Every change to a view property might invalidate the view. Layouting and drawing happens once, after onBindViewHolder has returned.
Yes, ViewHolders are recycled. That's the reason for their existence: so that expensive-to-build ViewHolders can be recycled to display different items. Yes, you need to zero out any changes to the contents of the ViewHolder that may have been made while an item was bound to it.
Rendering is also batched. Any change you make to a View property invalidates rendering or layout (as appropriate), and layout and rendering passes occur later. Property changes on views ultimately make calls to RequestLayout(), and/or RequestRender() to schedule layout and/or rendering passes at a later time. There is virtually zero cost for the second and subsequent changes.
I'm not quite sure when "a later time" is, but there's no delay involved. Right away, for all practical purposes, but batched. Something along the lines of process all queued messages, and when there are none, start a layout pass if it's been requested.
Rendering then takes place in a separate pass, after the layout pass has taken place.
Recycler view actual does a LOT of work figuring out where items were displayed, where items will be displayed and animating between the old and new location. This occurs in the layout pass, after you have notified the RecylerView that items in your adapter have changed. But it's done in a batch. You can toggle away on visibility to your hearts content. Only one render pass, and one layout pass takes place. (That's a sight simplification; atypically for an Android view, internally, RecyclerView, actually executes two layout passes, but that's not something you need to know or are supposed to know. It's a hack. I THINK you're being called during the first layout pass).
With respect to toggling bits and pieces in an onBindVieHolder call... rest assured that layout and rendering for the contents of the ViewHolder have been thoroughly and completely invalidated by the time you get the call. You are about to display something else, somewhere else. So go to it!

Should one not use data binding for RecyclerView item views?

I'm currently researching why some of my RecyclerViews do not perform very well. The UI lags especially when the whole content of the view needs to be drawn.
Using systrace I've come across a large frame in which the RecyclerView layouts its views:
As you can see the same views are drawn repeatedly. Inside the systrace I've found the following description on the RecyclerView frame:
OnLayout has been called by the View system. If this shows up too many times in Systrace, make sure the children of RecyclerView do not update themselves directly. This will cause a full re-layout but when it happens via the Adapter notifyItemChanged, RecyclerView can avoid full layout calculation.
The views of my RecyclerView are using google's data binding with observable values.
Meaning to update a TextView I don't use textView.setText(titleTxt) but title.set(titleTxt) where title would be of the type ObservableField<String>.
So evidently I'm not calling notifyItemChanged but only update observable fields on objects bound to the layout.
Is it wrong to use data binding in this case performance wise? If so, whats the point of data binding then? Mobile UI consists largely of lists.
Unfortunately Google left data binding behind with the arrival of Kotlin, so if you think it is slowing things down like it did with me, just stop using it or move to Kotlin all together. Kinda sucks for us to try and work with things that are constantly breaking and being left behind, but that's just what Google decided to become.

best practice for updating an adapter

I have an adapter that displays a grid of thumbnails with a text. These thumbnails are heavy to load, heavy to draw, etc.
The thumbnail gridview is constantly filled with new content, let's say, 1 new item every 2 seconds.
My adapter has a function that I call from outside to inject new items:
public void postNew(Item i) {
arrayStuff.put(i);
notifyDataSetChanged();
}
What happens is, with my current approach, when I insert a new element in the gridview, it refreshes everything, even if the added item is not going to be visible. The refresh process kind of breaks the experience, specially if the user is browsing the gridview and new content arrives.
How would you recommend improving this? is there a lighter 'notifyDataSetChanged()' or something like that?
I do not know of any lighter version of notify data set, but you can always use ListView.getFirstVisiblePosition and ListView.getLastVisiblePosition to determine whether your latest added position is visible, and only call notifyDataSetChanged if it is.
As for "heavy" bitmaps, as heavy as it is I think you should resample or scale it to the minimum size you need, using LruCache you can reduce the need of re-drawing on notify data set changed.
It sounds like you probably need to implement some form of caching, it's not very good memory management to have images which are not visible loaded into memory, ideally you would retrieve them from cache when they become (or are about to become) visible.
An alternative approach could be to add some form of visual indicator when new content arrives and then implement "pull down to refresh" or similar, then make a call to notifyDataSetChanged() on your adapter to refresh the content. I can imagine that refreshing every couple of seconds would not give a great UX because it would be hard to follow if the screen content is constantly changing.
You need create custom view(dynamic at runtime) that adds multiple imageview and appropriate textview, the container view should be LinearLayout, after that you can able to update a particular view or element.

How To Determine When ListView is Done Populating

I need to manipulate a ListView's children when it is done loading, but I can't seem to find a way to find out when this happens. When I set the adapter of my ListView, the method returns immediately, but the population of it's children views happens asynchronously. Is there a way to handle the event when my list is full of views?
You can either do a periodic polling to see if the list has been populated: write a while() loop to check once in a while. Or you could wait for a fixed amount of time (say 30 ms) before doing your next operation. These are not recommended methods, but should solve your problem.
Use getChildCount() to retrieve the shown views and also use a counter to keep track of how many views have been loaded asynchronously. I think I understand your question right.

Categories

Resources