RecyclerView inside RecyclerView, app crashing due to high memory consumption - android

I wanted to create a recycler view of photo albums and when the user taps on an album, it expands to grid layout showing images in that album.
For example, non-expanded:
and expanded:
The layout of outer recyclerView item only contains a textView, checkbox, and an invisible recyclerview that becomes visible on click of the item.
I declared the outerAdapter like this:
photosVideosAdapter = new PhotosVideosAdapter(getContext(),new ArrayList<PhoneAlbum>());
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setAdapter(photosVideosAdapter);
Here is the onBindViewHolder of outer adapter:
holder.tvFolder.setText(phoneAlbums.get(position).getName());
ArrayList<PhonePhoto> phonePhotos = phoneAlbums.get(position).getAlbumPhotos();
InnerAdapter innerAdapter = new InnerAdapter(context, phonePhotos);
holder.innerRecyclerView.setHasFixedSize(true);
holder.innerRecyclerView.setLayoutManager(new GridLayoutManager(context,3));
holder.innerRecyclerView.setAdapter(innerAdapter);
.... OnClickMethod to toggle visibility of innerRecyclerView
And onBindViewHolder for inner adapter:
Glide.with(context)
.load(phonePhotos.get(position).getPhotoUri())
.override(200,200)
.into(holder.imgThumbnail);
The problem is that if inner recyclerView contains more than 200 items, the app crashes due to high memory usage. But as you can see, I'm using glide to load images and also RecyclerView shouldn't create all the views at once. But, what I can see is that the inner RecyclerView is creating all the item views at once which is causing the app to crash.
How can I fix this problem? Any help will be appreciated.

Trust me, dont go with recyclerview inside recyclerview.
I had the similar situation in my current app where my colleague built solution by using nested recyclerview.
I rewrote the entire logic using insert and delete with animation and multiple view type.
It will involve some extra code to manage it. But the result would be quite satisfying.
In fact, I used same logic in iOS collectionview as well. App on both platform is live.

Try add this to your manifest.xml
android:largeHeap="true"

Your memory consumption is likely to high because you retrieved all the photos from the album at once with the line:
ArrayList<PhonePhoto> phonePhotos = phoneAlbums.get(position).getAlbumPhotos();
I assume you are also pulling all the photos at their original quality, so having 200+ full size photos on phone's RAM all at once could be the cause of the crash.
One way you could fix this would be to load in lower resolution previews of the photos into RAM, and once a photo is actually being displayed in the RecyclerView you could load a full resolution photo.

Related

Android ListView with Custom Adapter, slow loading

I have a Custom Adapter for the ListView. The Layout has three images, some text. When loading the Listview, it takes a while, because of the images. Its something like Posts.
Same app in iOS is loading very fast, I think that UITableView works different than Listview.
Is there a way, in place to load all posts, only load for example 3 posts and when the user scrolls the Listview down, load the next 3, scroll down, load the next 3 and so on. This could give a better performance.
Normally, android listview work that way. Let's say there are 5 views that user can reach at the moment. ListView creates 9 views and when user scrolls it loads the bottom ones. You can think it this way. Your main problem is how are you loading your images and create the custom view. There is a common pattern for custom adapters which handles the fast recycling views(ViewHolder pattern). You should checkout the link for ViewHolder pattern. https://www.javacodegeeks.com/2013/09/android-viewholder-pattern-example.html
It probably loads slow because the images should be resized every time, if you could save thumbnails it would go much faster.
Have you tried to use Recycler View instead? As long as I know using the Recycler View is the best practice nowadays. You can find a very good tutorial here:
http://www.vogella.com/tutorials/AndroidRecyclerView/article.html
You use the Recycler View almost the same way you do with List View: adapter, viewHolder, etc. It's good to mention though that you need to pay special attention to the use of the LayoutManager since the Recycler View it itself doesn't "know how to draw" the stuff on the screen.

Can RecyclerView preload the first n item on the list?

I have a listing of some item and I am using Glide to load an image onto an ImageView. The problem is, if a particular item, scroll-enter the screen, that is the only time I saw image is being drawn using Glide. This is noticeable when I put crossfade when the image has finished loading.
I know from the start the number of items needed and couple of other items like header and footer. I am thinking of just making a very long view to handle everything and dynamically add those known items at onCreate and just use ScrollView which I have done. I currently implemented a simpler way using a RecyclerView but I having trouble regarding the timing of onBindView call.
What I want is to load first 5 items before they become visible into the screen. This I hope will trigger the method where I bind the image onto the ImageView with Glide before it is fully visible into the screen.
I followed this tutorial and its not working for me. Here is my current setup:
mComicListingAdapter = new ComicListingsAdapter(getContext(), Constants.CHAPTERS, (ComicListingsAdapter.Callback) getActivity());
mRecycleViewHomeScreen.setAdapter(mComicListingAdapter);
mRecycleViewHomeScreen.setHasFixedSize(true);
mRecycleViewHomeScreen.setItemViewCacheSize(42);
mRecycleViewHomeScreen.setLayoutManager(pcll);
Is this possible in RecyclerView? Or maybe I should try dynamically creating ImageView and appending them to a parent View as I have done so. It looks great but there is a lot of things to do in my case using this method. I am just curious if I can direct RecyclerView to load n-amount of items in advance.
Thanks!
I've found a blog post where author suggest to extend LinearLayoutManager and override getExtraLayoutSpace().
And it worked. More items were preloaded depending on the number of pixels returned in getExtraLayoutSpace().
P.S. I've not checked yet how that affects performance, especially on pre-lollipop devices.
Sample:
val customLayoutManager = CustomLayoutManager(this)
recyclerView = findViewById<RecyclerView>(R.id.recyclerView).apply {
layoutManager = customLayoutManager
... //
}
class CustomLayoutManager(context: Context?) : LinearLayoutManager(context) {
override fun getExtraLayoutSpace(state: RecyclerView.State?) = 2000
}
Views are created dynamically at runtime and you can only access view items at onBindViewHolder method of adapter. You want to load images before setting adapter to recycler view. Firstly the getItemCount method is being called which will look for number of items and then onCreateViewHolder method is called to draw a row into your RecyclerView.
So this is not possible in RecyclerView to access items before setting any Adapter. You need to achieve this in some other way by dynamically creating an ImageView, for example.

Update only one element in one CardView (Android Recyclerview)

Having read a lot of questions answering "how to update one single row of ListView or RecyclerView (which would be notifyItemChanged(position)), i have a more "nested" question:
My custom RecyclerView ImageRecycler holds a couple of CardViews. In these cardviews, we can find a ProgressBar and an ImageView.
Logic: I open a file chooser elsewhere, choose an image, starts displaying / fading it in for 2 seconds in the newly added CardView, while i am also uploading it to a server. So during my image animation, i want to be updated for the upload progress as well. For the last said, i use a LocalBroadcastReciever calling notifyItemChanged(position).
To load the ImageView, i use Glide for asynchronous image loading in my adapter:
File f = new File(entries.get(position).getFilePath());
Glide
.with(main)
.load(f)
.diskCacheStrategy(DiskCacheStrategy.RESULT)
.crossFade(2000)
.into(holder.iv1);
Which leads to my problem:
Image is uploading, notify-method called a few times, which uploads the ProgressBar (yay!), but also makes Glide reloading the image again and again, so the screen starts flickering.
I already tried to animate the image only for the first time, after that, i only call my view holder to just set the already loaded image:
holder.iv1.setImageBitmap(entries.get(position).getBitmap());
But this makes the scrolling of my RecyclerView very faltering.
So how can i refresh only the ProgressBar as a part of CardView, but allowing the "old" image to stay?

Out of memory error when nesting gridview inside listview

I'm trying to add a GridView of images (ImageView) for my android application. I've successfully implemented lazy loading and caching of images and all of my high quality images work flawlessly when I have a simple GridView. However, the problem occurs when I try to nest my GridView inside a ListView.
When I simply have a GridView, the app only has to keep reference to images that are currently being rendered on the screen. But when I nest a GridView of, say 100 hi-res images, inside a ListView, the app tries to keep a reference to all 100 images, when only 20 of them are being rendered on the screen at the time. I found this out through my Log output, where the app would print a message every time an image is "read". I would see the app read 20 images that are being displayed on the screen, but then it would keep going and read more images. When it reaches around 100~ images, the app would run out of memory and crash. This behavior does not happen when I have just the GridView; the app will just read 20 images, and load more as I scroll the screen.
How can I make it so that my ListView does not try to load all elements inside a child view (especially the one that's not being displayed on screen)?
Thanks!
EDIT:
For those wondering why I would want a GridView inside a ListView, it's because I'm trying to create something like this:
Except I'd like to have it in a ListView, as opposed to ExpandableListView.
You are getting this Exception because in an expanded GridView all the data is loaded in memory, if your data is too much it will cause OutOfMemory Exception.The solution is: divide your data in small parts and then try.
You can not nest scrollable Views in Android. Components such as ListView, GridView, ScrollView can't be nested into each other.
You should take a look at Fragments (http://developer.android.com/guide/components/fragments.html) and stop to re-think your UI a little bit.
And... for large datasets, image/data lazy-loading is not enough... it is highly recommended that you implement a paging mechanism that would allow the user to page trough the results/content.
Let's say you'll display 20 items at a time. You will have to add a Next and a Previous buttons on the sides of the GridView UI (or you can do it the cool way with gestures)... and tweak your GridView's Adapter logic to pull only 20 items from your Model or from the Server at a time.
I found a really simple implementation of paging with a grid view among my bookmarks: http://paulononaka.wordpress.com/2011/03/19/how-to-implement-a-paging-feature-in-android/ ... I bet there are better ways to do it, but it could give you ideas.
Good luck!

Gridview getChildAt hidden fields

I have issue with Gridview in Android.
Context :
I have a gridview, with a list of images downloaded (around 100 images).
To do it more convenient for user, a first gridview is load, with inside 100 images on which is a logo Loading.
After that, I start an AsyncTask that :
In onProgressUpdate => Update image with gridview.getChildAt
In onPostExecute => change adapter of gridview with the list of bitmaps downloaded
My issue is in onProgressUpdate.
When using getChildAt(position), I realized that position means position of the item in the visible rect. So there is two issues :
=> First, hidden images are not updated (it's why I'm doing an setAdapter in PostExecute)
=> Second, if I scroll while downloading, it forget the first images, and some other issues while scrolling, but difficult to express with word ...
My question is a little easy, but I didn't find in Android Reference, and also after Google searches :
- How is it possible to update a view in a gridview by its real position, and not by visible position ?
Thanks a lot
Views that are not visible do not exist, so you can't update them. You can only update your backing model to have the correct information once the user scrolls to a particular item. So what you need to do is to retrieve the images and save them in a cache (or the model itself) that you can access when rendering a particular view.
However, I would suggest that there's no need to retrieve any images the user is not seeing yet.

Categories

Resources