-1

I have the following singleton running for my Android application

public class ListsApplication extends Application {
public DbxDatastoreManager datastoreManager;
public HashMap<String, ViewItemContainer> itemsSync;
public Typeface Font;
public boolean Fetch;

private static ListsApplication singleton;
public static ListsApplication getInstance() {
    return singleton;
}

@Override
public void onCreate() {
    super.onCreate();
    singleton = this;
    itemsSync = new HashMap<>();
    Font = Typeface.createFromAsset(getAssets(), "fonts/GoodDog.otf");
    Fetch = true;
}}

then from the Home activity I retain the singleton instance through

ListsApplication app = app.getInstance

in the Home activity I setup a listener which is triggered from the datastore online server whenever there's a change in the online datastores through

private void setUpListeners() {
    app.datastoreManager.addListListener(new DbxDatastoreManager.ListListener() {
        @Override
        public void onDatastoreListChange(DbxDatastoreManager dbxDatastoreManager) {
            // Update the UI when the list of datastores changes.
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    app.Fetch = true;
                    Home.this.updateList();
                    app.Fetch = false;

                }
            }, 7000/* 3sec delay */);

            Toast toast = Toast.makeText(getApplicationContext(), "RECEIVED", Toast.LENGTH_SHORT);
            toast.show();
        }
    });

    updateList();
    app.Fetch = false;
}

I allow some time before running updateList() as the update to the datastore is not atomic so it takes like 2 seconds to have all the rows on the online datastore, ignore the app.Fetch for now, I'm going to explain later.

updateList() clears the ArrayList of items that have to populate the listAdapter and runs adapter.notifyDataSetChanged()

In my custom listAdapter, I've set the getView as follows:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    final ViewHolder mHolder;
    if (convertView == null) {
        /*Toast toast = Toast.makeText(context, "nullConvertView", Toast.LENGTH_SHORT);
        toast.show();*/
        mHolder = new ViewHolder();
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = inflater.inflate(R.layout.listitem, parent, false);


        mHolder.PetName = (TextView) convertView.findViewById(R.id.PetName);
        mHolder.BuyFood = (ImageView) convertView.findViewById(R.id.PetBuyFood);
        mHolder.PetImage = (RoundedImageView) convertView.findViewById(R.id.PetImage);





        convertView.setTag(mHolder);
    } else {
        mHolder = (ViewHolder) convertView.getTag();
    }
    updateItem(mHolder, this.getItem(position));return convertView;}

and updateItem(mHolder, this.getItem(position))

public void updateItem(final ViewHolder currentViewFromList, final DbxDatastoreInfo info){
    app = ListsApplication.getInstance();
    if (app.itemsSync.containsKey(info.id) && !app.Fetch){
        /*Toast toast = Toast.makeText(context, "not fetching "+info.title, Toast.LENGTH_SHORT);
        toast.show();*/
        ViewItemContainer cont = app.itemsSync.get(info.id);
        currentViewFromList.PetName.setTypeface(app.Font);
        currentViewFromList.PetName.setText(cont.PetName);
        currentViewFromList.PetImage.setImageBitmap(cont.PetImage);
        if (cont.BuyFood) currentViewFromList.BuyFood.setVisibility(View.VISIBLE);
        else currentViewFromList.BuyFood.setVisibility(View.INVISIBLE);
    }
    else{
        ViewItemContainer cont = new ViewItemContainer();
        try {
        Toast toast = Toast.makeText(context, "entrato"+info.title, Toast.LENGTH_SHORT);
        toast.show();

        DbxDatastore datastore = app.datastoreManager.openDatastore(info.id);
        datastore.sync();

        if (info.title!=null){
            currentViewFromList.PetName.setTypeface(app.Font);
            cont.PetName = info.title;
            currentViewFromList.PetName.setText(cont.PetName);
        }

        DbxTable table = datastore.getTable("ITEMS");
        DbxRecord record = table.get("INFO");

        if (record!=null && record.hasField("PETPICTURE")){
            byte[] b = record.getBytes("PETPICTURE");
            //Bitmap picture = Misc.ByteArrayToImg(b);
            cont.PetImage = Misc.ByteArrayToImg(b);
            if (cont.PetImage!=null) currentViewFromList.PetImage.setImageBitmap(cont.PetImage);
        } else currentViewFromList.PetImage.setImageResource(R.drawable.ic_launcher);


        if (record!=null){
            cont.BuyFood = record.getBoolean("BUYFOOD");
            if (cont.BuyFood) currentViewFromList.BuyFood.setVisibility(View.VISIBLE);
            else currentViewFromList.BuyFood.setVisibility(View.INVISIBLE);
        }


        datastore.close();
    } catch (DbxException e) {
        e.printStackTrace();
    }

        app.itemsSync.put(info.id,cont);
    }

}

basically when the application starts, app.Fetch is set to true, updateList() from Home creates the adapter and calls adapter.notifyDataSetChanged().

At this point the listAdapter getView is called for every item in the list, checks for the id as key in the hashmap and doesn't find it so it fetches data from the datastore, and inserts the data in the hashmap and on the view.

Up to this point it's all good. The listview is populated correctly, and when the views are scrolled the data is retrieved correctly from the hashmap instead than being fetched over and over from the datastore.

Then from another phone I add a new datastore, which triggers the listener and on my phone it shows "RECEIVED" toast, which means that the listener has been triggered. app.Fetch goes to true and then updateList() gets called.

If I check the app.Fetch value anywhere in updateList() it's still set to true, while if I check it when the getView code is running, it's set to false so it retrieves the data from the hashmap again instead than fetching the updated online datastores.

I think that the getView code starts to run in parallel with updateList function so it shows the following behaviour:

boolean true updateList() start adapter.notifyDataSetChanged() runs ListAdapter getView start updateList() ends boolean false getView checks boolean and since it's false at this point it retrieves the data from the hashmap.

Is there any way I can get updateList to wait until all the items in the listview run through getView?

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Venzo92
  • 1
  • 2

1 Answers1

0

Yes- you make sure that you only call notifyDataSetChanged on the UI thread. That way you won't ever call it while the list is loading. If you want to call it on an asychronous thread, use a runOnUiThread block to move that call to the UI thread. This will ensure you never try to update the list while its drawing and avoid the whole problem.

Gabe Sechan
  • 90,003
  • 9
  • 87
  • 127