2

I've been fooling around with Android but I'm stuck. I have a adapter class that sets an image in an imageview for each item in my gridView. I use the observer pattern to notify the activity that calls the update method and it refreshes the adapter with notifydatasetchanged and that invalidates the gridview itself and the imageView.

The problem is that when i change an object (which has a reference to a drawable), and try to update, nothing happens in the imageview. I've tried to debug and the objects are changed so i don't understand why it doesn't update...

Also, with this it works perfect to make the images disappear, so that's pretty akward..: shape.setImage(android.R.color.transparent)

@Override
    public void update(Observable arg0, Object arg1)
    {
    //  Toast.makeText(this, "I am notified",0).show();

        adapter.notifyDataSetChanged();
        gridView.findViewById(R.id.picture).invalidate();
        gridView.invalidate();

    }

This is an example of an object that needs to be showed:

public class Square extends Shape
{
    public Square()
    {
        setImage(R.drawable.square);
    }
    public String print()
    {
        return "s ";
    }
    public String getName()
    {
        return "Square";
    }
}

my adapter:

public class GridAdapter extends ArrayAdapter<Shape>
{
     Context context;
     int layoutResourceId;   
     List<Shape> data = null;



    public GridAdapter(Context context, int layoutResourceId, List<Shape> data)
     {
            super(context, layoutResourceId, data);
            this.layoutResourceId = layoutResourceId;
            this.context = context;
            this.data = data;
     }
     public View getView(int position, View convertView,ViewGroup parent) 
     {
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

            View rowView = inflater.inflate(R.layout.list_item, parent, false);

            rowView.setBackgroundResource(R.drawable.customshape);

            ImageView imgView =(ImageView)rowView.findViewById(R.id.picture);

            imgView.setImageResource(data.get(position).getImage());

            return rowView;
     }
DennisVA
  • 2,068
  • 1
  • 25
  • 35
  • Instead of `gridView.invalidate();` can you try `gridView.invalidateViews();`? This will force all the child views to be redrawn. Let me know if it works. – Manish Mulimani Oct 20 '14 at 12:36
  • see my answer here http://stackoverflow.com/questions/26362854/listview-how-to-access-items-elements-programmatically-from-outside/26567212#26567212 – TacB0sS Oct 26 '14 at 12:35

4 Answers4

3

I think the actual problem here is updating the drawable from outside the UI thread. notifyDataSetChanged() isn't the answer. The dataset didn't change, in my case at least. The dataset changed and I set the drawable to null (which worked). Later, when the image finished loading, I wasn't updating from the UI thread, so the GridView didn't know it was supposed to learn about the invalidation of the ImageView.

The answer in my case was to use AsyncTask which automatically uses the UI thread and thread syncs.

Note that notifyDatasetChanged() is for when the getViewId() changes in the adapter — which isn't the same as and actually has nothing to do with when the drawable changes. Also note that setImageDrawable() automatically invalidates the ImageView. There's actually no need to call ImageView.invalidate() or any other invalidate(). You just have to update the drawable in the UI thread, and AsyncTask comes with all the right baggage to not only do it at the right time, but in such a way that the GridView can learn about it before it updates the various canvases and drawable caches.

I learned most of the above from the Google Android dev pages and from source diving for hours on end. Curiously, source diving in Android Studio (ctrl-B) is actually easier than slogging through all the docs hoping to stumble on the right paragraph that explains the problem. Though, the problem is explained nicely here:

http://developer.android.com/training/displaying-bitmaps/process-bitmap.html

jettero
  • 835
  • 2
  • 13
  • 26
  • "I wasn't updating from the UI thread, so the GridView didn't know it was supposed to learn about the invalidation of the ImageView." If it is the reason of that problem Android has a nice exception:`android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.` which prevent you from doing that. – mmlooloo Oct 22 '14 at 03:02
  • I haven't seen it trip that. Slogging through sources from setImageDrawable and tracing back, I can't see where it would fire that, though I would expect it to for sure. That's not the only problem. You also have to time it so you're updating before it considers the drawable finished and caches it. Clearing the cache is problematic. I spent like two hours trying to find a way to clear the cache and really really invalidate and force a re-draw. AsyncTask avoids the second problem even if the first doesn't exist. If you update after the cache is finalized, you'll only see it *sometimes*. – jettero Oct 22 '14 at 03:46
1

I cannot comment, so I am going to share my thoughts here, let me know in the comments if I'm missing something.

Seems like you need to add the following method in your GridAdapter:

public void setData(List<Shape> stuff) {
    data.clear();
    data.addAll(stuff);
    notifyDataSetChanged();
}

Now, wherever in your activity you created the adapter, you should have a List that was passed to create the GridAdapter. Let's call that initial list 'data1'.

Assuming by 'refresh' you mean that you want to modify an existing item. You need to find that item in data1 and make the changes. Then you need to find the GridAdapter instance that you have already created and then use the setData method and pass data1.

Hope this is not totally useless info for you.

Note: it would be helpful if you posted more of your code.

Hahn
  • 424
  • 3
  • 11
  • 1
    I think this is technically incorrect. setImageDrawable() already refreshes. It should be enough unless something else is wrong. notifyDataSetChanged() is for when the data set changed, which isn't what this is about. – jettero Oct 27 '14 at 00:14
0

The problem is that when i change an object (which has a reference to a drawable), and try to update, nothing happens in the imageview.

Lets have a look at the doc:

public void notifyDataSetChanged()

Notifies the attached observers that the underlying data has been changed and any View reflecting the data set should refresh itself.

the only reason that when you call notifyDataSetChanged() and it dose not update your gridview is that your adapter data set and your object are two different things. that means your gridview data has not changed. you must change those objects that you passed to your gridView constructor (List data). if you change any of them and then you call notifyDataSetChanged() it will work. The references of your object and the adapter data must be the same.

another things that I have seen on the net is you must change the data set of adapter by manipulating it with functions like (add(), insert(), remove(), clear(), etc.).

mmlooloo
  • 18,937
  • 5
  • 45
  • 64
-4

Problem solved lol, i just made a new adapter instead and it worked

DennisVA
  • 2,068
  • 1
  • 25
  • 35
  • 1
    this can't be the right way to do it... I'd like to know the right way, but google wouldn't go through the trouble of passing convertView to getView if this was the right thing to do. – jettero Oct 20 '14 at 01:01
  • update the underlying data that populates the listview. then call `adapter.notifyDataSetChanged()`. new adapter is not the right way as commented above – Raghunandan Oct 22 '14 at 08:17