Embedding AdMob Adverts in Android ListViews

Posted in Android by Dan on May 9th, 2011

Whilst working on StackAnywhere (a new Android client for Stack Overflow and other Stack Exchange websites), I was trying to figure out how best to incorporate AdMob adverts in the UI so that I could make the app free to download. The stumbling block was that, with some space already taken up by the tabbed navigation, the additional space occupied by the adverts tended to make the user interface very cramped with little room for content. This was particularly problematic in landscape orientation since the adverts are the exact same dimensions as in portrait mode which means they take up proportionally more room vertically whilst wasting space horizontally.

StackAnywhere with an advert in a ListViewSince most of StackAnywhere’s activities feature ListViews, I eventually hit on the idea of embedding the adverts in the lists. This is not an entirely original idea, there are other apps that do something similar, but it has the advantage of displaying the advert prominently while also allowing the user to scroll it off screen and out of their way.

Implementing this solution is reasonably straightforward although there are a couple of minor pitfalls to avoid. The decorator pattern is an ideal approach to use since it allows you to add adverts to an existing list adapter without having to modify the code for that adapter. My naive first attempt fell foul of a ClassCastException when adding the AdView to the list, and I also had some focus issues to resolve. The code below is a simplified version of that used in StackAnywhere. The implementation simply adds one advert to the top of the list. You could easily modify this to show more adverts if you wish. For example, you could show an advert after every 10 items. The code assumes that you are using the new Google AdMob SDK (version 4.0.4 in this instance), rather than the old AdMob SDK.

import android.database.DataSetObserver;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import com.google.ads.AdRequest;
import com.google.ads.AdSize;
import com.google.ads.AdView;
 
/**
 * List adapter decorator that inserts adverts into the list.
 * @author Daniel Dyer
 */
public class AdvertisingAdapter extends BaseAdapter
{
    private static final String ADMOB_PUBLISHER_ID = "YOUR_ADMOB_ID_HERE";
 
    private final Activity activity;
    private final BaseAdapter delegate;
 
    public AdvertisingAdapter(Activity activity, BaseAdapter delegate)
    {
        this.activity = activity;
        this.delegate = delegate;
        delegate.registerDataSetObserver(new DataSetObserver()
        {
            @Override
            public void onChanged()
            {
                notifyDataSetChanged();
            }
 
            @Override
            public void onInvalidated()
            {
                notifyDataSetInvalidated();
            }
        });
    }
 
    public int getCount()
    {
        return delegate.getCount() + 1;
    }
 
    public Object getItem(int i)
    {
        return delegate.getItem(i - 1);
    }
 
    public long getItemId(int i)
    {
        return delegate.getItemId(i - 1);
    }
 
    public View getView(int position, View convertView, ViewGroup parent)
    {
        if (position == 0)
        {
            if (convertView instanceof AdView)
            {
                return convertView;
            }
            else
            {
                AdView adView = new AdView(activity, AdSize.BANNER, ADMOB_PUBLISHER_ID);
                // 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);
                // Default layout params have to be converted to ListView compatible
                // params otherwise there will be a ClassCastException.
                float density = activity.getResources().getDisplayMetrics().density;
                int height = Math.round(AdSize.BANNER.getHeight() * density);
                AbsListView.LayoutParams params
                    = new AbsListView.LayoutParams(AbsListView.LayoutParams.FILL_PARENT,
                                                   height);
                adView.setLayoutParams(params);
                adView.loadAd(new AdRequest());
                return adView;
            }
        }
        else
        {
            return delegate.getView(position - 1, convertView, parent);
        }
    }
 
    @Override
    public int getViewTypeCount()
    {
        return delegate.getViewTypeCount() + 1;
    }
 
    @Override
    public int getItemViewType(int position)
    {
        return position == 0 ? delegate.getViewTypeCount()
                             : delegate.getItemViewType(position - 1);
    }
 
    @Override
    public boolean areAllItemsEnabled()
    {
        return false;
    }
 
    @Override
    public boolean isEnabled(int position)
    {
        return position != 0 && delegate.isEnabled(position - 1);
    }
}