Hot questions for Using Glide in listview

Question:

I have a ListView with around 5-6 rows, every row is a RecycleView displaying horizontal Fragments. Cards (like Netflix, HBO apps for instance)

Considerations:

  • Every RecycleView has around 30-50 items.
  • Every ViewHolder has a "big" picture as background (around 300x260dp depending on the device screen size, the width is around 90% of the device screen)
  • The images are loaded by using Glide (code below)

I created a cache (SparseArray) for the RecycleViews in the ListView Adapter to keep the state of every RecycleView, otherwise every time it gets out of the screen it restarted to position 0.

By doing this, just scrolling every RecycleView to the end, makes some of them get a freeze.

Tried several workarounds:

  • Reviewed MAT and have refactored some code for avoiding leak because of the static context
  • MAT obviously display a massive heap of memory because of the images
  • Reduced the size of the images
  • Hide the images
  • Create a cache of ViewHolders (didn't work)
  • Create a memory cache for the Images once Glide download it
  • Making the ViewHolders setIsRecyclable to false

But, none of this options has worked, the only way to guarantee the app is not getting a freeze, is avoiding the ListView cache (And losing the last position you visited, if you moves the View out of the screen)

Glide Code (I tried several options here: avoiding Bitmap, avoiding memory cache, updating the disk Strategy to ALL)

Glide.with(context).load(url)
                .asBitmap()
                .skipMemoryCache(true)
                .thumbnail(0.5f)
                .centerCrop()
                .error(placeHolderId)
                .diskCacheStrategy(DiskCacheStrategy.RESULT)
                //.priority(Priority.NORMAL)
                .dontAnimate()
                .into(new SimpleTarget<Bitmap>() {
                    @Override
                    public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
                        if (bitmap != null) {
                            imgView.setImageBitmap(bitmap);
                            //cache.put(url, bitmap);
                        } else {
                            imgView.setBackground(null);
                        }
                    }
                });

Any advice/suggestions? Maybe there is another option to keep the state of the View without the cache?

ListView Adapter Code:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
            case TYPE_VENUE_FILTER: {
            View v;
            // Create a new view into the list.
            LayoutInflater inflater = (LayoutInflater) context
                        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            if (inflater != null) {
                v = inflater.inflate(R.layout.fragment_venue_filter, parent, false);
            }

            // We need to get the exact filter to be displayed
            int index = position - FIRST_VENUE_FILTER_ROW;
            if (index < this.filters.size()) {
                VenueFilter filter = this.filters.get(index);

                // Recalculate the distance order
                filter.sortVenuesForLocation(this.lastLocation);

                // Set data into the view.
                VenueFilterFragment.loadFragment(this.context, v, filter, this.fm);
            }

            return v;
        }
}

UPDATE 11/04

I finally figure out.

  • Reused the converView on the Adapter as suggested
  • Included a ScrollListener in the RecycleView and storing in the bean the last scroll position, for recover it when is revisited.

This code is in the Adapter, for inflating the specific row (getView)

    // We should reset or recover the scroll state
    LinearLayoutManager llm = (LinearLayoutManager) filterContent.getLayoutManager();
    if (filter.offset > 0) {
        if (llm != null) llm.scrollToPosition(filter.offset + 1);
    } else {
        if (llm != null) llm.scrollToPosition(0);
    }

    // We need to clear every time. Because we are reusing the view
    recycleView.clearOnScrollListeners();

    // We need to create a new specific scroll listener
    recycleView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);

            // Keeping the offset for a future load
            LinearLayoutManager llm = (LinearLayoutManager) recyclerView.getLayoutManager();
            if (llm != null) filter.offset = llm.findFirstVisibleItemPosition();
        }
    });

Answer:

This is a very broad and complex question. Your layout is complex and thus it will take some effort to make it efficient. Things that you can do to improve the performance:

  • All RecyclerViews should use view holders where possible. Define multiple view types if needed. The parent RecyclerView will probably have only one view type which is an item with a child RecyclerView. Child RecyclerViews might also share view holders by having a RecycledViewPool. More info here: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.RecycledViewPool.html
  • Make sure all layouts are as flat as possible. The more view types you have the more important this becomes.
  • Glide is a good choice for image loading. Make sure you use caching where possible. Cache images once they are downloaded to reduce the impact on network. Use placeholders or progress bars when downloading an image. Reset each ImageView state when the view is reused to avoid flickering.
  • Cancel image loading for items that you no longer need to load. This should also help you to reduce the networking activity.
  • You can restore the scrolling position too, there shouldn't be any problem here. This is a separate topic though, you can refer this thread for more details: How to save RecyclerView's scroll position using RecyclerView.State?
  • And keep an eye on MAT, yes. It's important to make sure you don't use too many resources which is quite easy in this case.

I hope that helps. Good luck!

Question:

  • Code to load album art

        if(cursor != null && cursor.moveToFirst()){
    
            int titleColumn = cursor.getColumnIndex(MediaStore.Audio.Media.TITLE);
            int idColumn = cursor.getColumnIndex(MediaStore.Audio.Media._ID);
            int artistColumn = cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST);
            int albumColumn = cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM);
            long albumID = cursor.getColumnIndex(MediaStore.Audio.Albums.ALBUM_ID);
    
            do {
                long thisID = cursor.getLong(idColumn);
    
                String thisTitle = cursor.getString(titleColumn);
                String thisArtist = cursor.getString(artistColumn);
                String thisAlbum = cursor.getString(albumColumn);
    
                Glide.with(this)
                        .load(albumID)
                        .asBitmap()
                        .into() //How do I return a value?
    
                medialist.add(new SongInfo(thisID, thisTitle, thisArtist, thisAlbum, //Bitmap variable));
            }
            while (cursor.moveToNext());
    

I'm trying to load the album art into a listview using Glide. However, I'm having trouble in the;

medialist.add(new SongInfo(..);


Answer:

you are doing wrong: use this:

medialist.add(new SongInfo(thisID, thisTitle, thisArtist, thisAlbum,albumID));

Now, in your listview adapter class, get medialist and in glide method: define:

Context con;

Now in Constructor of adapter:

public CustomAdapter(List<Medialist> items, Context context) {
        medialist= items;
        this.con = context;
    }

Glide.with(con)
                    .load(medialist.getAlbumID)
                    .asBitmap()
                    .into(imageview)

Question:

I am trying to load an image into an imageView that's in a listview's cell with Glide.

Here is the XML for the image view :

<ImageView
   android:id="@+id/imageView"
   android:layout_width="0dp"
   android:layout_height="match_parent"
   android:layout_margin="2dp"
   android:scaleType="centerInside"
   android:adjustViewBounds="true"
   android:layout_weight="1"
   android:background="@android:color/white" />

And in the getView of my adapter:

Glide.with(activity)                           
 .load(url)
 .placeholder(R.drawable.placeholder)
 .into((ImageView) view.findViewById(R.id.imageView));

However, when the images first finish loading, they are not fitting the image view as they should. For example:

But then when I scroll the listview and come back to the image, it's being correctly rended:

What to do here? Thanks for your help.


Answer:

You may want to add .dontAnimate() after.load()

Question:

I have a ListView inside a ScrollView that will be displaying images and text. The text is displayed perfectly. The images however aren't. If I scroll the list slowly - all the images load perfectly without fail, but if I go really fast they never get loaded. I think it's how I set up where I am calling the method but this is my first day in Android. Below are the resources, layouts, and classes.

How I added Text and an ImageView inside list:

<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TableRow>
        <ImageView
            android:id="@+id/img"
            android:layout_width="50dp"
            android:layout_height="50dp"/>

        <TextView
            android:id="@+id/txt"
            android:layout_width="wrap_content"
            android:layout_height="50dp" />

    </TableRow>

</TableLayout>

The class to go along with it:

public class someClass extends ArrayAdapter<String>{

    private final Activity context;
    private final ArrayList<String> web;
    ImageView imageView;
    FirebaseStorage storage = FirebaseStorage.getInstance();
    StorageReference storageRef = storage.getReference();
    final ArrayList<Uri> uriList = new ArrayList<>();
    public galveon_character_creation_spelllist(Activity context,
                      ArrayList<String> web) {
        super(context, R.layout.someClass, web);
        this.context = context;
        this.web = web;

    }
    @Override
    public View getView(int position, View view, ViewGroup parent) {
        LayoutInflater inflater = context.getLayoutInflater();
        View rowView = inflater.inflate(R.layout.someClass, null, true);
        TextView txtTitle = (TextView) rowView.findViewById(R.id.txt);


        imageView = (ImageView) rowView.findViewById(R.id.img);
        txtTitle.setText(web.get(position));
        getImages(position);

        return rowView;
    }

    public void getImages(Integer position) {
        storageRef.child("FolderRef/" + web.get(position) + ".png").getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
            @Override
            public void onSuccess(Uri uri) {
                //imageView.setImageURI(null);
                //imageView.setImageURI(uri);
                Glide.with(getContext())
                        .load(uri) // the uri you got from Firebase
                        .placeholder(R.drawable.unknown) //this would be your default image (like default profile or logo etc). it would be loaded at initial time and it will replace with your loaded image once glide successfully load image using url.
                        .diskCacheStrategy(DiskCacheStrategy.ALL) //using to load into cache then second time it will load fast.
                        .animate(R.anim.fade_in) // when image (url) will be loaded by glide then this face in animation help to replace url image in the place of placeHolder (default) image.
                        .fitCenter()//this method help to fit image into center of your ImageView
                        .into(imageView); //Your imageView variable

            }
        }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception exception) {
                // Handle any errors
                imageView.setImageResource(R.drawable.unknown);
            }
        });
    }
}

web are just text that line up with the picture names. Also when the activity loads the list, all the "cells" that are see do not load, but when you scroll down slowly and back up the images appear (if you scroll slowly).

If I need to post more information I can. Still learning how Android works.


Answer:

try, giving imageview as a parameter to the getImage instead of global variable. Eg:

    // inside getView
    ImageView imageView = (ImageView) rowView.findViewById(R.id.img);
    getImages(position, imageView);

    // GetImage
    public void getImages(Integer position, ImageView im) {
       ....
    }