0

I have a Listview with two adapters. They receive an Array with some data and when scrolled to bottom of the listview more data is loaded into the Array and here the listview should add the new data.

Question: How can I make the listview reflect the new data and why isn't it updated when I call adAdapter.notifyDataSetChanged(); ?

Fragments inner class where I download data & update UI in onPostExecute:

@Override
    protected void onPostExecute(String result) {

        super.onPostExecute(result);

        numberofpagesshown = numberofpagesshown + 1;

        if(numberofpagesshown == 1 ) {

            list = (ListView) getActivity().findViewById(R.id.search_listview_movie);

            adapter = new SearchListViewAdapter(getActivity(), mylist);

            adAdapter = new BannerAdListView2(getActivity(), adapter);

           // list.setAdapter(adapter); this works when adapter.notifyDataSetChanged(); also is in the else statement but here I don't use the BannerAdListView2 adapter which i want to?

list.setAdapter(adAdapter);

            bar.setVisibility(View.GONE);
        }
        else {

          //  adapter.notifyDataSetChanged();   // Here the listview should get refresh with the new data

              adAdapter.notifyDataSetChanged();

            bar.setVisibility(View.GONE);

        }


        // Attach the listener to the AdapterView onCreate
        list.setOnScrollListener(new EndlessScrollListener() {
            @Override
            public void onLoadMore(int page, int totalItemsCount) {
                // Triggered only when new data needs to be appended to the list
                // Append new items to AdapterView
                if(pageNumberInt < totalPagesInt){

                   ++incre;
                   new DownloadJSON().execute();
                }

            }
        });


    }

Here is my SearchListViewAdapter:

public class SearchListViewAdapter extends BaseAdapter{

Context context;
ArrayList<HashMap<String, String>> data;
HashMap<String, String> mylist = new HashMap<>();


static String url;
static String title;

public SearchListViewAdapter(Context a, ArrayList<HashMap<String, String>> d) {
    context = a;
    data = d;
}

public int getCount() {
    return data.size();
}

public HashMap<String, String> getItem(int position) {
    return data.get(position);
}

public long getItemId(int position) {
    return position;
}

public View getView(final int position, View convertView, ViewGroup parent) {

    // Avoid unneccessary calls to findViewById() on each row
    final ViewHolder holder;

    /*
     * If convertView is not null, reuse it directly, no inflation
     * Only inflate a new View when the convertView is null.
     */

        if (convertView == null) {
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.search_list_item, parent, false);

            holder = new ViewHolder();

            holder.poster = (ImageView) convertView.findViewById(R.id.list_image);

            holder.textForTitle = (TextView) convertView.findViewById(R.id.search_title);

            holder.textForTitle.setTag(position);

            convertView.setTag(holder);
        }
        else{

            // Get the ViewHolder back to get fast access to the TextView
            // and the ImageView.
            holder = (ViewHolder) convertView.getTag();
            holder.textForTitle.setTag(data.get(position));
        }



        mylist = data.get(position);


        final String mediaType = mylist.get("media_type");

        if (mediaType.equals("movie")) {

            String posterPath = mylist.get("poster_path");
            url = "http://image.tmdb.org/t/p/w185" + posterPath;
            title = mylist.get("title");
        } else if (mediaType.equals("tv")) {

            String posterPath = mylist.get("poster_path");
            url = "http://image.tmdb.org/t/p/w185" + posterPath;
            title = mylist.get("original_name");
        } else {

            String posterPath = mylist.get("profile_path");
            url = "http://image.tmdb.org/t/p/w185" + posterPath;
            title = mylist.get("name");

        }


        // set image url correctly
        // sizes for image 45, 92, 154, 185, 300, 500


        // load image url into poster
        Picasso.with(context).load(url).into(holder.poster);

        // set title to textview

        holder.textForTitle.setText(title);





    return convertView;


}



class ViewHolder {
    ImageView poster;
    TextView textForTitle;
}

 }

And my BannerAdListView2 adapter:

public class BannerAdListView2 extends BaseAdapter
     {

Activity activity;
Context context;
BaseAdapter delegate;
int k = 7;
int baseItems;
int noAds;



// Constructor takes in a BaseAdapter
public BannerAdListView2(Activity activity, BaseAdapter delegate ) {

    this.activity = activity;
    this.delegate = delegate;

    baseItems = delegate.getCount();
    noAds  = baseItems / k;
    LayoutInflater mLayoutInflater = (LayoutInflater) this.activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

}


         @Override
         public int getCount() {
             // Total count includes list items and ads.
             return baseItems + noAds;
         }

         @Override
         public Object getItem(int position) {
             // Return null if an item is an ad.  Otherwise return the delegate item.
             if (isItemAnAd(position)) {
                 return null;
             }
             return delegate.getItem(getOffsetPosition(position));
         }

         @Override
         public long getItemId(int position) {
             return position;
         }

         @Override
         public int getViewTypeCount() {
             return delegate.getViewTypeCount() + noAds;
         }

         @Override
         public int getItemViewType(int position) {
             if (isItemAnAd(position)) {
                 return delegate.getViewTypeCount();
             } else {
                 return delegate.getItemViewType(getOffsetPosition(position));
             }
         }

         @Override
         public boolean areAllItemsEnabled() {
             return false;
         }

         @Override
         public boolean isEnabled(int position) {
             return (!isItemAnAd(position)) && delegate.isEnabled(getOffsetPosition(position));
         }

         private boolean isItemAnAd(int position) {


             if (position < k) return false;
             // Calculate current offset caused by ads already embedded

             if (position==k){
                 return true;
             }
             else {
                 return isItemAnAd(position-k);
             }


         }

         // Get the position that is offset by the insertion of the ads
         private int getOffsetPosition(int position) {
             int currentNoAds = position / k;
             return position - currentNoAds;

         }



         @Override
         public View getView(int position, View convertView, ViewGroup parent) {

             // Display every n list items
             if (isItemAnAd(position)) {
                 if (convertView instanceof AdView) {
                     // Don’t instantiate new AdView, reuse old one
                     return convertView;
                 } else {
                     // Create a new AdView
                     AdView adView = new AdView(activity);
                     adView.setAdSize(AdSize.BANNER);
                     String bannerId = "My unit id";
                     adView.setAdUnitId(bannerId);

                     // Disable focus for sub-views of the AdView to avoid problems with
                     // trackpad navigation of the list.
                     for (int i = 0; i < adView.getChildCount(); i++)
                     {
                         adView.getChildAt(i).setFocusable(false);
                     }
                     adView.setFocusable(false);

                     // Convert the default layout parameters so that they play nice with
                     // ListView.

                     float density = activity.getResources().getDisplayMetrics().density;
                     int height = Math.round(AdSize.BANNER.getHeight() * density);
                     AbsListView.LayoutParams params = new AbsListView.LayoutParams(
                             AbsListView.LayoutParams.MATCH_PARENT,
                             height);
                     adView.setLayoutParams(params);
                     AdRequest bannerIntermediateReq = new AdRequest.Builder()
                             .addTestDevice(AdRequest.DEVICE_ID_EMULATOR)
                             .addTestDevice("d9e108ab") //means that a test ad is shown on my phone
                             .build();

                     adView.loadAd(bannerIntermediateReq);

                     return adView;
                 }
             } else {

                 // Offload displaying other items to the delegate
                 return delegate.getView(getOffsetPosition(position) ,
                         convertView, parent);

             }
         }
     }

Edit: Changed the constructor of BannerAdListView2

 // Constructor takes in a BaseAdapter
public BannerAdListView2(Activity activity, final BaseAdapter delegate ) {

    this.activity = activity;
    this.delegate = delegate;
    delegate.registerDataSetObserver(new DataSetObserver() {
        public void onChanged() {
            baseItems = delegate.getCount();
            noAds = baseItems / k;
        }

        public void onInvalidated() {
            notifyDataSetInvalidated();
        }
    });
    baseItems = delegate.getCount();
    noAds  = baseItems / k;
    LayoutInflater mLayoutInflater = (LayoutInflater) this.activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

}

And in fragment

@Override
    protected void onPostExecute(String result) {

        super.onPostExecute(result);

        numberofpagesshown = numberofpagesshown + 1;

        if(numberofpagesshown == 1 ) {

            list = (ListView) getActivity().findViewById(R.id.search_listview_movie);

            adapter = new SearchListViewAdapter(getActivity(), mylist);

            adAdapter = new BannerAdListView2(getActivity(), adapter);

            list.setAdapter(adAdapter);

            bar.setVisibility(View.GONE);
        }
        else {

            adapter.notifyDataSetChanged();   

            bar.setVisibility(View.GONE);

        }

     }
Mat0
  • 1,165
  • 2
  • 11
  • 27
  • hehe you just don't give up ... it is obviously because of `public int getCount() { return baseItems + noAds; }` ... still this implementation is overcomplicated to analize it ... just draw it on the paper... – Selvin Oct 21 '15 at 16:21
  • No I fixed the problems I had earlier. But this should include both? The adview count and the view from other adapter? – Mat0 Oct 21 '15 at 16:22
  • 1
    baseItems is only set in BannerAdListView2 constructor .. so even if inner Adapter changed its item count outer adapter will don't know about it ... – Selvin Oct 21 '15 at 16:23
  • So I should call BannerAdListView2 everytime data changes? This wouldn't seem very practical? – Mat0 Oct 21 '15 at 16:26
  • 1
    no ... rather implement DataSetObserver in outer adapter ... and register it for inner adapter ... then in onChanged() of outer adapter do the same stuff as in constructor( baseItems = delegate.getCount(); noAds = baseItems / k;) and invalidate self(notifyDataSetChanged - outer adapter) ... now when you change the data in inner adapter just call notifyDataSetChanged on inner adapter ... seriously, draw it first on paper(I would do this) it is not an easy task ... – Selvin Oct 21 '15 at 16:30
  • Thanks I would not have thought of this :) – Mat0 Oct 21 '15 at 16:42
  • I've edited the question. I've tested this, and it works however there's a lot of frame skips now? Can you explain why they appear after this? – Mat0 Oct 21 '15 at 18:31
  • please clean your code up to debuggable. – Mohammad Hossein Gerami Oct 21 '15 at 19:10
  • @Mat0 maybe recurency in isItemAnAd ... – Selvin Oct 21 '15 at 19:26

1 Answers1

1

Try this solution ... DecorerAdapter:

public static abstract class DecorerAdapter extends BaseAdapter {

            public int DECORER_ITEM_TYPE;
            private final BaseAdapter mInnerAdapter;
            private final int mRepeatAfterEvery;
            private int mCount;

            public DecorerAdapter(BaseAdapter innerAdapter, int repeatAfterEvery) {
                mInnerAdapter = innerAdapter;
                mRepeatAfterEvery = repeatAfterEvery;
                mInnerAdapter.registerDataSetObserver(new DataSetObserver() {
                    @Override
                    public void onChanged() {
                        notifyDataSetChanged();
                    }

                    @Override
                    public void onInvalidated() {
                        notifyDataSetInvalidated();
                    }
                });
                setupAdapter();
            }

            @Override
            public void notifyDataSetChanged() {
                setupAdapter();
                super.notifyDataSetChanged();
            }

            private void setupAdapter(){
                mCount = mInnerAdapter.getCount();
                mCount += (mCount  + mRepeatAfterEvery - 2) / (mRepeatAfterEvery - 1);
                DECORER_ITEM_TYPE = mInnerAdapter.getViewTypeCount();
            }

            @Override
            public int getCount() {
                return mCount;
            }

            @Override
            public Object getItem(int position) {
                if(position % mRepeatAfterEvery == 0)
                    return null;
                return mInnerAdapter.getItem(calculateInnerPosition(position));
            }

            private int calculateInnerPosition(int position) {
                return position - (position  + mRepeatAfterEvery - 1) / mRepeatAfterEvery;
            }

            @Override
            public long getItemId(int position) {
                if(position % mRepeatAfterEvery == 0)
                    return -1;
                return mInnerAdapter.getItemId(calculateInnerPosition(position));
            }

            @Override
            public int getItemViewType(int position) {
                if(position % mRepeatAfterEvery == 0)
                    return DECORER_ITEM_TYPE;
                return mInnerAdapter.getItemViewType(calculateInnerPosition(position));
            }

            @Override
            public boolean hasStableIds() {
                return mInnerAdapter.hasStableIds();
            }

            @Override
            public int getViewTypeCount() {
                return mInnerAdapter.getViewTypeCount() + 1;
            }

            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                if(position % mRepeatAfterEvery == 0)
                    return getDecorerView((position  + mRepeatAfterEvery - 1) / mRepeatAfterEvery, convertView, parent);
                return mInnerAdapter.getView(calculateInnerPosition( position), convertView, parent);
            }

            public abstract View getDecorerView(int position, View convertView, ViewGroup parent);
        }

code with usage:

package pl.selvin.decoreradapter;

import android.database.DataSetObserver;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;

public class MainActivity extends AppCompatActivity {

    /*** paste the DecorerAdapter class here ***/

    public static class MyListFragment extends ListFragment{
        final ArrayList<String> strings = new ArrayList<>();
        private BaseAdapter innerAdapter;

        @Override
        public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
            menu.add("Add rnd(10) more");

            super.onCreateOptionsMenu(menu, inflater);
        }

        final Random rnd = new Random();
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            final String[] toAdd = new String[rnd.nextInt(10)];
            int start = strings.size();
            for(int i = 0; i < toAdd.length; i++)
                toAdd[i] = (start + i) + "";
            Collections.addAll(strings, toAdd);
            Toast.makeText(getActivity(), "Added " + toAdd.length + " count: " + strings.size(), Toast.LENGTH_SHORT).show();
            innerAdapter.notifyDataSetChanged();
            return true;
        }

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setHasOptionsMenu(true);
            for(int i = 0; i < 4; i++) {
                strings.add(i + "");
            }
            innerAdapter = new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, strings);
            setListAdapter(new DecorerAdapter(innerAdapter, 5) {
                @Override
                public View getDecorerView(int position, View convertView, ViewGroup parent) {
                    if(convertView == null) {
                        convertView = new TextView(getActivity());
                        convertView.setBackgroundColor(Color.RED);
                    }
                    ((TextView)convertView).setText("AdView: " + position);
                    return convertView;
                }
            });
        }

        @Override
        public void onListItemClick(ListView l, View v, int position, long id) {
            String item = (String)l.getItemAtPosition(position);
            Toast.makeText(getActivity(), "Item click: " + (item == null ? "ADVIEW" : item), Toast.LENGTH_LONG).show();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction().replace(android.R.id.content, new MyListFragment(), "LIST").commit();
        }
    }
}

It repeats decorer view at every element passed to the constructor ...

all you need to do is implement getDecorerView in the similar way as getView in normal adapter

how it looks?

Selvin
  • 6,598
  • 3
  • 37
  • 43
  • This is much smoother! Could you explain why adding (mCount + mRepeatAfterEvery - 2) / (mRepeatAfterEvery - 1) to mCount (in void setup adapter )? I don't quite understand the math there? – Mat0 Oct 21 '15 at 19:46
  • 1
    smart ceiling ... it just count how many decorer views will be for mCount if you will repeat it at every mRepeatAfterEvery – Selvin Oct 21 '15 at 19:49
  • http://stackoverflow.com/questions/7139382/java-rounding-up-to-an-int-using-math-ceil#answer-21830188 ... (int) Math.ceil((double) mCount / (mRepeatAfterEvery -1))) – Selvin Oct 21 '15 at 19:53
  • the same story: `return position - (position + mRepeatAfterEvery - 1) / mRepeatAfterEvery;` how many decore view we added already? `(int) Math.ceil((double) mCount / mRepeatAfterEvery))` as you can see it can be done without recurency – Selvin Oct 21 '15 at 19:55
  • Okay thanks this is much appreciated. Could similar approach be used for a gridview? Or would that need to be made using a staggered gridview (which is more similar to a listview)? – Mat0 Oct 21 '15 at 19:56
  • you can absolutly try(the same code) ... as BaseAdapter can be used in any AdapterView ... – Selvin Oct 21 '15 at 19:57
  • Even if I use a pre-set among of columns? Wouldn't it need to be set as auto-fit columns? Ex. Say I have 4 columns and it should add an adview every 6th entry. Wouldn't it just create the adview in one column and not fill the entire row? – Mat0 Oct 21 '15 at 20:01