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.
Related
Hi, I'm working on a little complicated dynamic recyclerview right now.
To explain, there is an item in MainRecyclerView like the picture.
This Mainitem is dynamically added/deleted whenever the button is clicked.
And as you can see from the picture, the Mainitem has a SubRecyclerView inside it again.
SubRecyclerview is also added/removed dynamically through the button in the photo.
At least as a beginner, I think that to implement this, there must be an Adapter for MainRecyclerView and an Adapter for SubRecyclerView inside it.
But I think I've seen somewhere that these nested Adapters are difficult to implement and inefficient.
What do you think?
Is there any better way? Or a library for this.
Nested RecyclerView's are fine if implemented in right way.
Performance improvement of RecyclerView's in general could be done using:
RecyclerView caching,
GPU rendering optimisation,
RecyclerView.RecycledViewPool,
RecyclerView prefetch
And, Most important thing is that RecyclerView doing his job -> recycling views.
Additional content that might help you to improve RecyclerView performance:
Gpu Rendering -> https://developer.android.com/topic/performance/rendering/inspect-gpu-rendering
Slow rendering -> https://developer.android.com/topic/performance
If you don't know what is causing performance issue, then try isolated metod by testing part by part manually. Eg. If you have 10 ViewHolders, start measuring performance with first one by disabling all other ViewHolders, then try to increase ViewHolders types in adapters and measure again.
I have a rather complicated List with nested RecyclerViews. I get it that nested RecyclerViews aren't the best solution, but in my case it is one of few solutions that create structured code and meet the requirements. I have attached an image of the structure. You can take telegram as an example to improve your understanding of the structure. Basically I have an outer RecyclerView RV-1 with Items RV-1-Item and an inner RecyclerView RV-2 with Items RV-2-Item. So far so good, my problem is that the outer RecyclerView recycles views as intended, but if one of the RV-1-Items comes into view, all ViewHolders of RV-2 are created (That means that sometimes more than 100 ViewHolders are created). To sum it all up my question is how to force the inner RecyclerView RV-2 to recycle ViewHolders as well.
I know that the inner RecyclerView RV-2 has to have a hight of wrap_content because it depends in the count of the inner items, also i cannot set setHasFixedHeigth(true) (and I don't know if it would help) because during runtime new RV-2-Items can be added into RV-2. I also tried to set setNestedScrollingEnabled(false) on RV-2 because I read a lot about it online but it didn't help me either.
So basically this is how I configure
RV-1
layoutManager = LinearLayoutManager(context)
isNestedScrollingEnabled = false
RV-2
setHasFixedSize(true)
layoutManager = LinearLayoutManager(context).apply {
reverseLayout = true
}
In addition to that I have some ItemDecorators but they only create the space between the items, so they shouldn't have to do anything with the problem.
To sum it all up:
The outer RV-1 recycles it ViewHolders as intended but the inner RV-2 creates all ViewHolders at once, even if they are not on screen. I assume that this is the case because RV-2 has a height of wrap_content and when the layout_height need to be measured it needs to create all views. THE QUESTION: Is there a way to force RV-2 to recycle its views?
EDIT:
Also I am using a shared RecycledViewPool between all RV-2 RecyclerViews but that isn't really related to the problem, because even if the ViewHolders are shared between the RecyclerViews, an RV-2 RecyclerView shouldn't create ViewHolders that aren't visible when it is initialised.
EDIT 2:
A lot of comments and related questions say that two vertical nested RecyclerViews isn't a possible thing in android, in case all visitors of this question think the same my question is: How would you implement such a structure. It is obvious that I could make a single view which has a IM (Round Image View) and RV-2-Item and just make the IM invisible when it isn't needed. In my opinion this somehow makes the structure more complicated. Furthermore a requirement is that the IM on the left side of RV-1-Item must have the ability to move up and down in RV-1-Item, which is obviously easier with my current structure.
EDIT 3: (My last one I promise)
The Problem I have shown can be solved by using the approche I explain in my EDIT 2, even if it isn't the best solution it would work. But the issue is that I have an even more complex screen where this approche wouldn't work anymore because I have three nested RecyclerViews. I could get that number down to two with the approche of EDIT 2 but I would still be left with two nested RecyclerViews and I cannot think of a workaround that could solve the problem of the remaining two nested RecyclerViews. I attached an image of the even more complex screen which contains a the interface of the app with marked sections to help you to understand the structure.
(Not quite an answer to your specific question in solving "how to not get the RecyclerView to create all items at once", but something that most likely will fix your specific problem by not using nested recyclerviews at all)
I would suggest (in a quite similar way as already suggested in this answer), to flatten your feed into one recyclerview
(No matter how much you tweak your nested recyclerview architecture, imho it will never be as performant than having just one recyclerview, and as you don't need nested scrolling (I guess), just one recycler view should be your best option).
I would propose to not think of your feed in the way your data is structured, but in a way you want to show it and how it can be split into smaller items which are "look alikes" / consist of the same things.
From your screenshot I would see for example the following items / view types for each chat item:
Chat header (the thing with the icon and the text "New Group")
the user badge (the picture with the text "Jürgen")
a message item (one bubble of text, so e.g. in your screenshot at the bottom there would actually be 3 of those items, one for each message)
The section with the date and the action/reply items.
Those items are way smaller than a whole chat item, and therefore can be faster created / recycled.
For each of those items, create a view-type and a view-holder, and treat them as seperate recycler-view items.
The recyclerview will, when the getItemViewType method is correctly used, create / prepare the correct type of view for the position you need.
For this to work, the adapter needs to add some logic, as your data most likely will be structured something like
a list of chats, and each chat has a name and some messages to display
and we need it as
the first 6 elements are for the first chat, where the first position
is the header, the second the user badge, the next 3 items are message
items and then we need an action item.
So you basically need to calculate how many recyclerview items you will need to show each single chat-item, which could be a calculation along the lines:
1 chat header item + 1 user badge item + 3 message items + 1 action/reply item = 6
This calculation needs to be performed for each chat item of your data list separately.
So if you only have this single chat item in your list of data to display, you actually need to tell the adapter to create 6 items (by returning in this case 6 at getItemViewCount()).
Then, you need to tell the adapter using the getItemViewType(position: Int) function, at which position of the recyclerview which type of view the adapter needs to prepare.
So there you again need some logic to say that e.g. on position 0 the chat header for the first chat item should be, at position 1 the user badge for the first chat item, at position 2-4 message items should be, on position 5 the action item and at position 6 the chat header for the second chat should be and so on
(again, the logic then needs to be in place for all chat items, and it can get really messy / complicated, as to calculate each chat items view types for a position, e.g. all prior chat element view counts need to be recalculated, too (in order to know at which recycler-view position your current chat item starts)).
As this tends to blow your adapter up, I would suggest (if you don't already do so), to get some manager / delegate architecture in there.
So e.g. have a delegate for each view type, and a manager which calculates the number of recyclerview items / view types needed for each chat item.
Just for reference:
Some time ago we had a situation similar to yours
(a recycler-view with a design similar to a social media feed, which should show the first n comments in the feed and we displayed the comments for each feed item (which was a recyclerview item) with another recyclerview in the item) and also after some troubles with performance which we could not manage to resolve just flattened the recyclerview, and never had performance troubles again.
A lot of comments and related questions say that two vertical nested RecyclerViews isn't a possible thing in android
This is not true; whoever says this is not a thing has not done it and thinks it's not possible. It is possible, albeit with complications, side-effects, and most likely, the annoyance of your users when they tap around trying to scroll up/down and the wrong touch interceptor wins.
Why is this a problem?
On iOS, when you try to do something that the platform devs didn't think it was good, most people and other devs scream at you: don't fight the framework!!!.
On Android, we see the craziest Java (and now Kotlin) implementations of things that makes you wonder what are we -developers- learning at school and what are we teaching?! and yet nobody says anything (for the most part) :p
The truth is, you're trying to design a complicated user interaction and data transformation, and yet, your attempt is biased by trying to use the data "as you have it" (which implies dealing with these two different RV/Adapters), as opposed to do what one should do: transform the data for presentation.
This leads me to the next question:
How would you implement such a structure.
Well, for starters, I don't know how your data looks like, nor where it's coming from; I don't know what your users can do with your data, outside of the obvious scrolling.
I also don't know how your data wants to be presented, aside from your mock up.
But I do know the situation very well. A list of things, which also contain their own list of things.
Case: The List of List
It is doable; you can have a list and inside said list, have another list. I've done it. I've seen it done by others. I've used it. I also never liked the idea of having this "small" scrollable thing, fighting to see who scrolls first when I tap "the wrong place".
I would not do this. If the inner-list is big (say more than 3 items per outer item), I would not present it as scrollable content.
What I would do (considering the things I do not know about your problem) is to have a single list displaying all the content properly flattened.
This has a issue with your content:
What if the inner-lists are super long, wouldn't this cause them all to be displayed? YES, and that's why I wouldn't do it this way if the data (as you described) can have 100 items. An options is to display the 3 first items with a "more" link to now open the inner-list "full screen"; this is 10 times better than the nested list from a user's PoV and from the technical aspect of it.
Another alternative, is to keep this single long list (RV-1) and let users "expand" the list to launch another full-screen list depicting the contents of RV-2, in a separate window. This is even better.
The time you'll spend implementing this and getting rid of the mess of code you probably have right now, will make you wonder why didn't you suggest this in the first place.
If this is something you absolutely cannot do, then I cannot offer you much more advice, for now you're tied to unknown to me business/product rules. Ultimately, the price will be paid by the users of your app, when they have to scroll that nightmare :)
Take a Step Back
Let me be clear, I am not criticizing you or your solution; I'm merely pointing out that, in my experience, this "pattern" you have here is not a good user experience.
Format your data for presentation, not the other way around. Your data should be properly shaped so it can be properly presented with the tools you have.
You're fighting against the tools Android is giving you; you're giving a RecyclerView (and its adapter) a lot of new problems to deal with when it already has a lot going on.
Think about it: RecyclerViews have to do a lot of things; Adapters must also conform to a few interfaces, ensure things are dispatched as soon as possible, calculate Diffs (if using a ListAdapter<T,V>), etc. Activities/Fragments? They have a lot on their plates dealing with ... well "Android"; now you're asking all these components to also handle a complicated scenario of scrolling content, touch recognition, event handling, view inflation, etc.
All this, while expecting each view to take 16ms or less (to stay above 60 FPS scrolling speed, your view/viewHolder should not take more than 16ms to do all it needs.
Instead, I'm asking you to take a step back, grab the data you have, compose it, transform it, map it, and create the data structure that can better serve the components you have (a RV + Adapter + a simple View).
Good luck :)
I am actually developing an Android application on which I should display dynamic forms based on metadata contained inside JSON documents. Basically the way it works (without the details) is that a JSON document represent the structure of a form:
{
"fields": [
{
"name": "fieldA",
"type": "STRING",
"minCharacters": 10,
"maxCharacters": 100
},
{
"name": "fieldB",
"type": "INTEGER",
"min": 10,
"max": 100
},
{
"name": "fieldC",
"type": "BOOLEAN_CHECKBOX",
"defaultValue": true
}
...
],
"name": "Form A"
}
What I'm doing actually when the application receive one of those JSON documents is that it loop through each fields and parse it to the appropriate view (EditText, Checkbox, custom view, etc.), adding a tag to the view (to be able to retrieve it easily) and add the view to a LinearLayout. Here is a pseudo-code of how it is working actually:
//Add form title
linearLayout.addView(new TextView(form.name));
//Add form fields
for(Field field: form.fields) {
View view;
switch(field.type){
case STRING: view = new EditText();
...
}
view.setTag(field.id);
linearLayout.addView(view);
}
The issue with this is that with large forms (like >20 fields), it need to inflate lot of views and the UI thread suffer a lot. Another point to take into account is that a single screen may have multiple forms (one after another vertically sorted).
To avoid overloading the UI thread I thought of 2 possible solutions:
Using a RecyclerView.
Using Litho by Facebook.
But multiple questions comes to me when considering these 2 solutions:
Is it a good use case to use Litho? Or using a RecyclerView is enough?
What about the state of my views? If I use a Recycling pattern, would I be able to keep the state of each of my fields (even those off-screen) and so being able to save the form without losing data?
If I use a Recycling pattern to display one form, how would I handle multiple forms? Can we have nested RecyclerView? Forms need to be displayed one after another like inside a vertical RV but if forms themselves are RV, how should I handle this?
This is more a "good practice" question and giving the right way or one of the right way of achieving my goal than a need of a specific answer with code example, etc.
Thank's in advance for your time.
When architecting for the mobile application I would like to address the following questions:
Is it a good use case to use Litho? Or using a RecyclerView is enough?
Are the views are being recycled properly:
What does it mean to us is consider, creating 40-50 view per screen and as user moves out of the view, system should not mark all views for GC rather it should be inside some kind archived list and as we require it again we should be able to fetch from it.
Why do we need to that: GC is the costliest operation which would cause app rendering to be jitter, we try to minimize the GC to called at this point by not clearing the views
For this I would like to go with litho, justification is here as your requirement seems to have more of variable count of viewtypesreference
Conclusion: Litho +1, RecyclerView +0
What about the state of my views? If I use a Recycling pattern, would I be able to keep the state of each of my fields (even those off-screen) and so being able to save the form without losing data?
Saving EditText content in RecyclerView This is one the component but same logic should be appliced to checkbox or radiobutton as well. or as in state-maintenance for litho is here
Conclusion: Litho +1, RecyclerView +1 both has specific API's to achieve state maintenance
If I use a Recycling pattern to display one form, how would I handle multiple forms? Can we have nested RecyclerView? Forms need to be displayed one after another like inside a vertical RV but if forms themselves are RV, how should I handle this?
This has to be addressed with user experience plus technical capability: As per user behaviour IMHO,I discourage the nested vertical scroll however others were able to achieve it you can easily find on how to in SO. Optimal solution would be to have horizontal scroll within either Andriod's or litho's recycler view as here
NOTE: If you need to know implementation details, please raise it as separate question, I would be happy to help there
UPDATE #1:
The issue with this is that with large forms (like >20 fields), it need to inflate lot of views and the UI thread suffer a lot.
UI creation/layout has to be performed at the backend only adding to the view has to be done on UI thread. And litho does it in-built. However same can be achieved native recycler view as well but you have to move off the UI thread and post periodically to UI.
Ok, you have two separate problems here. One is overwork the UI thread, and the other is to keep the state of your anonymous views. About the first part:
1.-Litho could help you with this. But you have to move all your logic towards litho components instead of android widgets. Since I don't know your codebase, I don't know how hard this can be. A recyclerview would help with view recycling, but that only matters if you are well, using a list.
2.-It could, as long as you have a way to keep a representation of the widget's state that you can pass to the adapter and then back to the view (I'm assuming you generate all the windows by code and then have zero reference to them) and so. It sounds messy, and it is messy, so I won't try it.
3.-You can, but is messy. Best approach in this case would be having horizontal recyclerviews inside a vertical recyclerview. Nesting recyclerviews inside another recyclerview with the same direction creates funny problems, like "Why this cell is not scrolling". I would avoid using the recyclerview as a parent if the view does not need it.
Now, to the solutions:
A) UI Overloading: According to your pseudocode, you aren't inflating stuff. You are creating java objects which happens to be subclasses of View. That's good, because creating objects in a background thread is far easier than inflating (Parsing XML and use it as arguments to generate identical copies of a given resource by invoking constructors) stuff in a background thread. While a LinearLayout context constructor requires an UI thread to be executed, other things, like textviews, don't. So you can create the latter ones inside an asynctask and after you are done generating your whole hierarchy, execute the methods that need the UI thread and add the generated layout to the window. For the view classes that don't support being created as java objects asynchronously, you can have an XML file with just that component, like the linearLayout, and create then asynchronously with the support package asyncLayoutInflater. this solution can be implemented in any codebase and would allow you to make your UI generation completely asynchronous.
B)Keeping track of the view state: Again, I'm assuming your view hierarchy is anonymous. If so, what you need is to generate an interface you can use as a contract to invoke both state saving and state loading from a lifecycle aware component, like the activity. After creating such interface, subclass the widgets and create a subscription/event bus system in each widget that saves/loads the state from the widget every time is triggered. That way, each of of the components on the screen would be able to remember their state while remaining anonymous.
Just use the RecyclerView and create views on runtime (you are not inflating, you are instantiating)
Dynamically creating and adding views should not slow the UI thread considerably on mid-range devices. If it does, do investigate for bottlenecks elsewhere
You can perform a simple benchmark by adding/removing/setting text with lots of views dynamically inside a RecyclerView or even a LinearLayout hosted by a ScrollView, and you'll see it goes smooth
Use jetpack composer provided by android
https://developer.android.com/jetpack/compose
I have a mix of 10-15 custom views and fragments to be shown in a vertical list. I am not sure if RecyclerView has any advantage in scenarios where all views are dissimilar. RecyclerView seems to add lot of boiler-plate code, and I think the only advantage I would get is easier enter/exit animation.
My custom views/fragment also make web-service call on being created. We don't cache web-requests for business reasons. My understanding is that RecyclerView would trigger these web-service calls on each binding, resulting in redundant calls and visible latency. Comparatively ScrollView should load the views once, and it keeps them all in memory, avoiding multiple calls.
Is my understanding correct ? I need some help understanding performance implications with ScrollViews, in the given scenario.
ScrollView
With a ScrollView, all of its subviews will be created at once, regardless of visibility on screen. If using a ScrollView for your solution, you'll probably want to "listen" for when its subviews become visible to update their content, using placeholders initially. You could also build something that will fetch the content in a background thread. This may get more complex than you want very quickly.
RecyclerView
A RecyclerView provides the advantage of deferring creation of child views until they become visible automatically, and can re-use child views with common layouts.
By using different "item view types" for each of your children, you'll disable the "recycling" part of RecyclerView, but still get the benefit of deferring the creation of views until they are scrolled into view.
RecyclerViews do provide a fairly structured pattern for you to work with via the Adapter and ViewHolders. Though not personally familiar with it, RecyclerView also has a RecyclerView.ViewCacheExtension which is intended to give the developer control over caching of views.
Overall, the advantage of late binding (don't create and load views that might never be viewed) and the flexibility of the RecyclerView will probably yield good results for you.
First of all you have to decide what you are using View or Fragment or maybe both. Don't compare View with Fragment there is a common misconception about these two, they are not similar, actually a Fragment is close to an Activity in terms of architecture and implementation.
Second, can you reuse some of these View/Fragment, if yes, then RecycleView can help you a lot.
After you decided about the topics above:
My understanding is that RecyclerView would trigger these web-service
calls on each binding
No, this is not true, the binding method is called whenever a new item is displayed (reused or newly created), you can implement adapter to perform the web API only once on an item, this is your choice.
I always go for RecycleView/ListView whenever possible, it helps to reduce the memory footprint and can reduce the implementation. In some cases, where there is no huge memory usage on views and I can't reuse some of the implementation, then I go for ScrollView, but I think twice before implementing it.
Suppose I have RecyclerView with hundreds of items. There are a few different view types. When I click on an item it has to change its UI. As I see it there are two options:
change the item to a different view type and notify the adapter that it changed so that it can inflate a new view to display the new state.
have both views in a single xml file. only one is visible at any time. Toggle between them using the setVisibility function when the state changes.
there are a lot of articles online about heterogeneous RecyclerViews using lots of view types, but i have yet seen one about the best practices when item views dynamically change and the RecyclerView becomes more heterogeneous over time.
which method is best to change an existing view in a recycler view? what are the tradeoffs?
The tradeoffs are pretty obvious.
Both UIs in a single View: depending on your views, this might:
Make the Views heavy to inflate and layout
Slow down the binding process, since you have to bind both UIs
Eventually, lag down the scrolling process.
On the other hand, you have a fast, precise control over what Views should change appearance at a certain time. Having both UIs in the same place allows for finer animations and transitions between one state and another.
Different View types: I think your case is not why view types were created in the first place - they are meant for having different childs at the same time, in different positions of the list. This:
Might slow down the transition. You have to be extremely careful on how you tell the adapter to reload the objects. As you can read anywhere, you might want to avoid notifyDataSetChanged(), which reloads any object, and rather use notifyItemChanged() or such. This requires some work and attention.
Does not allow (not easily, at least) for visual transition between one state and another.
The answer is that, IMO, only you can know. You should ask yourself:
Are these Views complex? Do they have a deep hierarchy? Would having both of them reduce performance (e.g. in the binding part)?
Would it be easy to understand which views are changed, and call notifyItemChanged(int position) in the second case? Reloading the whole dataset has a huge impact on performance, if you have lots of items.
Do you need some visual transition between one state and another? Maybe you want both UIs to fit into a single container (like different informations in a CardView)? If this is mandatory, then option 1 is better.
Do you have more than 2 UIs? If so, I would go with view types, so as not to host too many views in a single child layout.
and similar questions.