FANM FANM - 12 days ago 7
C# Question

ListView Lack of Memory in Xamarin.Android while scrolling

I'm quite new with Xamarin.Android, and I have been reading different sites and solutions, but most of them are in Java or they talked about some libraries that they don't have good documentation. My main problem is that from time to time while I'm scrolling the app, it crashes suddenly and is restarted, throwing a message of Lack of Memory. My current adapter has the following code:

public override View GetView(int position, View convertView, ViewGroup parent)
{
var inflater = LayoutInflater.From(parent.Context);
var view = inflater.Inflate(Resource.Layout.DishesListItem, parent, false);

var lblTitle = view.FindViewById<TextView>(Resource.Id.Title);
var imgThumbnail = view.FindViewById<ImageView>(Resource.Id.Thumbnail);

lblTitle.Text = data[position].name;

try
{
using (StreamReader sr = new StreamReader(Application.Context.Assets.Open(data[position].image)))
{
Drawable d = Drawable.CreateFromStream(sr.BaseStream, null);
imgThumbnail.SetImageDrawable(d);
}
}
catch
{
imgThumbnail = null;
}

return view;
}


I want to clarify that all my images are stored locally, I'm not downloading anything from the internet or accessing the SD Cards, pictures folders, etc. My images are located in the Assets folder because they are linked to a SQLite database and some IDs.

Currently, I'm just loading the Adapter once on the OnCreate method.

I found this solution, but it's not always working:
Tips & Tricks for Highly Performing Android ListViews

I notice that if you move from one Activity to another Activity and later you come back to the activity where is the ListView and you start scrolling up and down, it just crashes again.

Does anyone have experienced it before? And if you have, do you have a recommendation what can I change?

Answer

After my research has provided a successful result, I want to share my solution and I hope is going to help many developers in the near future. I consider these comments can be useful in Java or C#.

1) To design a extremely simple holder class is important, but it's not enough.

    private class MyViewHolder : Java.Lang.Object
    {
        public TextView lblTitle { get; set; }
        public ImageView imgThumbnail { get; set; }
    }

    public override View GetView(int position, View convertView, ViewGroup parent)
    {
        var inflater = LayoutInflater.From(parent.Context);
        MyViewHolder holder = null;
        var view = convertView;

        if (view != null)
            holder = view.Tag as MyViewHolder;

        if (holder == null)
        {
            holder = new MyViewHolder();
            view = inflater.Inflate(Resource.Layout.DishesListItem, parent, false);
            holder.lblTitle = view.FindViewById<TextView>(Resource.Id.Title);
            holder.imgThumbnail = view.FindViewById<ImageView>(Resource.Id.Thumbnail);
            view.Tag = holder;
        }

        holder.lblTitle.Text = data[position].name;

        try
        {
            holder.imgThumbnail.SetImageDrawable(data[position].image);
        }
        catch
        {
            holder.imgThumbnail = null;
        }

        return view;
    }

2) You need to send a very simple class to the adapter just with the information to load. You should not send more or less information if you do it, there is going to be a new lack of memory.

public class CompactedData
{
    public int id { get; set; }
    public string name { get; set; }
    public Drawable image { get; set; }
}

3) My instinct told me that I should optimize the class more and I need to stop loading any image in the Adapter, if you do that the holder is going to be sort of inefficient and it was true; the adapter was not good enough, you need to pre-load all images that you are going to add to the holder class if you want the maximum efficiency.

This section of code is the most inefficient in my experience and sooner or later is going to create a new lack of memory.

    try
    {
        using (StreamReader sr = new StreamReader(Application.Context.Assets.Open(data[position].image)))
        {
            Drawable d = Drawable.CreateFromStream(sr.BaseStream, null);
            imgThumbnail.SetImageDrawable(d);
        }
    }
    catch
    {
        imgThumbnail = null;
    }

This point is not going to be different if your images are local or not, you must pre-load it in the class that you're sharing to the Adapter before (in my case was called data).

    List<CompactedData> data;

    public PreviewDishesAdapter(List<CompactedData> data)
    {
        this.data = data;
    }

4) Finally, if your App is going to use several times the same Adapter or a similar one with images, this code is vital in the every activity in order to avoid any possible crash:

    public override void OnBackPressed()
    {
        base.OnBackPressed();
        GC.Collect();
        Finish();
    }

You must execute the Garbage Collector and Finish the Activity, if you keep it open there is going to be a new Lack of Memory at any point.

If most of us consider all these points, we can avoid using a Large Heap or modify other points that Googles doesn't recommend, which are very common suggestions in similar topics.