RecyclerView.Adapter onCreateViewHolder() is called after notifyDataSetChanged() - android-recyclerview

I use a RecyclerView which work well so far.
There are 5 Items visible on the screen, I have 7 ViewHolder which is OK.
If I scrolling up and down the number of instances of my ViewHolder is constant.
Now I check my app for memory consummation and look for memory leaks.
I notice every time I change my data and notify the adapter with :
notifyDataSetChanged();
I get every time 2 more ViewHolder.
The number of viewType is constant (only the data is changed).
Ten notifyDataSetChanged add 20 ViewHolder to my app and eat up memory.
Every ViewHolder hold a complex ViewModel for this reason I take care about unnecessary objects.
Is this a known bug in Android RecyclerView ?

You should try to calculate differences and notify item changes (preferably with a payload) to avoid recreating ViewHolders. You can use DiffUtil class for that.
If you can't do that, you need to increase size of your RecyclerView.RecycledViewPool which by default holds only 5 ViewHolders of each type:
// increases amount of recycled ViewHolders of type 0 (default) to 10
recyclerView.getRecycledViewPool().setMaxRecycledViews(0, 10);

Related

How to findViewById in RecyclerView after setting custom id to every element via adapter

I have a fragment that implements Recycler View with switches. RecyclerView sets id for every switch in id+=1 way. The problem is I can get nothing from these IDs as soon as calling throws NullableException. I understand that I have to call it correctly from parent, but I don`t get how to do it correctly.
There is the structure of code:
Fragment with ConstraintLayout(R.id.settingsScreen) -> RecylerView(R.id.recyclerView), that creates LinearLayouts(R.id.settingLayout) with switches(R.id.switch).
class ViewHolder{
val l : LinearLayout = view.findViewById(R.id.settingLayout)
}
if (holder.l.findViewById<SwitchCompat>(1100).isActivated){..}
throws java.lang.NullPointerException: Attempt to invoke virtual method 'boolean androidx.appcompat.widget.SwitchCompat.isActivated()' on a null object reference
It is very fragile to create view IDs yourself like this. They could easily collide with IDs generated by the Android build. So if you create IDs, you should be using ViewCompat.generateViewId() to do it safely.
But findViewById is something you generally want to avoid in the first place. It is slow. That's why View Binding is provided, to cache the views so you don't have to keep searching for them.
I highly recommend storing your Views in a collection instead of assigning them IDs. Then you can efficiently pull them from the collection when you need them. You just need to be sure the collection will be garbage collected when you're done with the views (don't create a collection that will outlive the screen the views are on).
You can get the view by using itemView property
Not sure of you are trying to do when you mean custom id.
You can get all views inflated by viewHolder with itemView.
this property is given to ViewHolder after you pass view as parameter in ViewHolder constructor, than ViewHolder will inflate views according with their id's.
than you can get view inside ViewHolder by:
itemView.findViewById<LinearLayout>(R.id.settingLayout)
itemView.findViewById<SwitchCompat>(R.id.switch)
You can look to see more options in google docs
If these are not what you asked for please let me know

Best way to release and reinitialize exoplayer in RecyclerView

I have two instances of Exoplayer with different mediasources.
Currently
Before initializing either of player. I am checking if any player attached to the surface if yes then releasing the player and initializing again with new operator
if(videoPlayerView.getPlayer() != null) {
videoPlayerView.getPlayer().release();
videoPlayerView.setPlayer(null);
}
videoPlayerView is my PlayerView in SimpleExoPlayer
But after View detached list item becomes black instead of playing again.
I used to do similar things with you.You need a global players pool to manager players.You needn't to release it everytime.Just call stop method.And when switch video,just reset source.To make it play instantly witout black, you need to prepare it.When activity is finishing, release all players.
I strongly suggest you have a single instance of Exoplayer, init in first use and release when activity or fragment (based on your usecase) destroyed

prepareLayout method of NSCollcectionViewLayout

I have hard time understanding the purpose of prepareLayout method of NSCollectionViewLayout.
According to the official apple documentation it is written
During the layout process, the collection view calls several methods of your layout object to gather information. In particular, it calls three very important methods, whose implementations drive the core layout behavior.
Use the prepareLayout method to perform your initial layout calculations. These calculations provide the basis for everything the layout object does later.
Use the collectionViewContentSize method to return the smallest rectangle that completely encloses all of the elements in the collection view. Use the calculations from your prepareLayout method to specify this rectangle.
Use the layoutAttributesForElementsInRect: method to return the layout attributes for all elements in the specified rectangle. The collection view typically requests only the subset of visible elements, but may include elements that are just offscreen.
The prepareLayout method is your chance to perform the main calculations associated with the layout process. Use this method to generate an initial list of layout attributes for your content. For example, use this method to calculate the frame rectangles of all elements in the collection view. Performing all of these calculations up front and caching the resulting data is often simpler than trying to compute attributes for individual items later.
In addition to the layoutAttributesForElementsInRect: method, the collection view may call other methods to retrieve layout attributes for specific items. By performing your calculations in advance, your implementations of those methods should be able to return cached information without having to recompute that information first. The only time your layout object needs to recompute its layout information is when your app invalidates the layout. For example, you might invalidate the layout when the user inserts or deletes items.
So I naively used this as a guide and rewrote my implementation of custom layout. I computed collectionViewContentSize and precomputed the array used in this method
- (NSArray<__kindof NSCollectionViewLayoutAttributes *>*)layoutAttributesForElementsInRect:(NSRect)rect;
such that in all 3 required methods I just return the cached values. And after this suddenly my collectionView became extremely laggy.
Apparently the method prepareLayout is called on every scroll.
Can anyone clarify what does it mean. Or maybe I do not understand anything?
The usual thing, if you don't want prepare called every time there is a scroll event, is to keep a CGSize instance property and implement shouldInvalidateLayout so that it returns YES only if the new bounds size is different from the stored CGSize property value.
Here's a Swift example, but I'm sure you can translate it into Objective-C:
var oldBoundsSize = CGSize.zero
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
let ok = newBounds.size != self.oldBoundsSize
if ok {
self.oldBoundsSize = newBounds.size
}
return ok
}
So this was my bad. Apparently if
- (BOOL)shouldInvalidateLayoutForBoundsChange:(NSRect)newBounds;
returns YES. Then this method is called. I just changed that this method returns NO and the method prepareLayout has been stopped called all the time.

MvxRecyclerView does not update after resuming from background

I have a MvxRecyclerView that gets filtered by a SearchView and ordered by options in a PopupMenu. If I background the app and resume it, changing options in the PopupMenu does not re-order the items in the MvxRecyclerView as it does before backgrounding but the SearchView filters fine without ordering.
Trying to solve a different issue this one got fixed.
I noticed FragmentInventoryTabView's OnResume was being called multiple times every time it came back from the background, which meant the ViewModel and the View were being instanced on every resume. They were being built with Mvx.IoCProvider.IoCConstruct() in the tab layout root ViewModel's ctor, so I changed those calls to Mvx.IoCProvider.GetSingleton() and registered the ViewModel types as singletons in the MvxAppStart.
Now I can order the items in the MvxRecyclerView after resuming back from the background. But OnResume is still being called multiple times.

Can C style blocks cause memory leaks?

I'm working on a kiosk style slideshow app. I have a UIScrollView which shows the slides, and a factory class, which generates the slides. The "slides" themselves are UIViewController subclasses, which are loaded out from XIB files and customized by the factory class. In my main view controller, I set up the scroll view and start a timer. The timer calls a "reload" method every N seconds, which handles the reload and call to the factory class.
The method that the factory class uses looks something like this:
- (SlideViewController *)slideFromManagedObject:(Slide *)managedObject{
NSInteger slideType = [managedObject slideType];
switch(slideType){
case kSlideTypeA:
{
//
// configure arguments here
//
return [[SlideViewController alloc] initWithArgument:argument] autorelease];
break;
}
//
// More types here...
//
default:
break;
}
}
I haven't yet gotten to the point of defining all of my cases, but the ones that are filled out seem to cause jumps in memory usage. If I add return [[[UIViewController alloc] init] autorelease]; right before the switch/case, I get no visible view, as expected, but I also don't see those memory increases. I'm not sure, but I suspect that it's the "C blocks" that I'm wrapping my slide generation code in.
Some things to note:
When the app starts, I see the memory plateau from about 400 kilobytes to around double that. Then, when the slides progress, any of the slides whose generation code is contained in curly braces is called, the memory plateaus upwards again.
This behavior only seems to happen once per launch - when the app loops through all of the slides, the plateaus to_not_ happen again. However if the app is backgrounded and then relaunched, the plateaus do occur again, consuming even more memory.
When I left the app to run overnight, for about 10 hours and forty minutes, the memory usage had slowly climbed from about 1.44 megabytes to somewhere closer to 1.57 megabytes. I suspect that there are/were some other leaks in there that may have been fixed by my tweaking, but the main jump from about 800 kilobytes to somewhere between 1.4 and 1.5 megabytes is still an issue.
Instruments does not report any leaks, but the plateauing concerns me.
What could be causing the increased memory?
EDIT:
So I don't think it's the blocks, since using an if/else seems to do the same thing.
Here's a screenshot of the Allocations instrument running:
Where could possibly be holding on to these views?
One possible explanation for what you are seeing is some caching that UIKit (I assume) is doing of your objects (don't know what they are, but I think of images mostly).
Caching is often used during transitions and for other internalities of UIKit.
UIKit empties its caches usually when a memory warning is received, so you could try and send one to see what happens. In actuality, I suspect that results of sending a memory warning will not be very easy to analyze, since all of your views are also unloaded, hence memory will go down forcibly. But you can try...
As to how sending a memory warning to the device (as opposed to the simulator), here you find an useful S.O. post.