jpelgrim
1/15/2013 - 2:46 PM

Simple Adapter which can handle multiple input cursors asynchronously (ideal for usage with CursorLoaders)

Simple Adapter which can handle multiple input cursors asynchronously (ideal for usage with CursorLoaders)

/* Copyright 2013 Alex Curran

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License. */

package com.espian.formulae.data;

import android.content.ContentResolver;
import android.content.Context;
import android.database.CharArrayBuffer;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import java.util.ArrayList;

/**
 * Created with IntelliJ IDEA.
 * User: Alex Curran
 * Date: 11/01/2013
 */
public class MultiContentAdapter extends BaseAdapter {

    public static final int TITLE = 0x100;

    private ArrayList<Integer> mIdTypeList;
    private ArrayList<Cursor> mCursors;
    private String[] mTitles;
    private String[] mColumnsForCursors;
    private LayoutInflater mInflater;
	private int mTitleId, mContentId;

    private int cachedCount = -1;

    public MultiContentAdapter(Context context, String[] titles, String[] columnsForCursors, int titleResId, int contentResId) {
        super();
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (titles.length != columnsForCursors.length)
            throw new IllegalArgumentException("titles and columnsForCursors must be the same length");
        mTitles = titles;
        mColumnsForCursors = columnsForCursors;
        mCursors = new ArrayList<Cursor>();
        for (String title : mTitles)
            mCursors.add(new EmptyCursor());
        mIdTypeList = new ArrayList<Integer>();
	    mTitleId = titleResId;
	    mContentId = contentResId;
        buildIdType();
    }

    @Override
    public int getViewTypeCount() {
        return 2;
    }

    @Override
    public boolean isEnabled(int position) {
        return !isTitle(position);
    }

    @Override
    public int getItemViewType(int position) {
        return isTitle(position) ? 1 : 0;
    }

    @Override
    public int getCount() {
        if (cachedCount < 0) {
            cachedCount = 0;
            cachedCount += mTitles.length;
            for (Cursor c : mCursors)
                cachedCount += c.getCount();
        }
        return cachedCount;
    }

    @Override
    public Object getItem(int position) {
        if (isTitle(position)) {
            int bit = mIdTypeList.get(position) - 256;
            return mTitles[bit];
        } else {
            int cursorIndex = mIdTypeList.get(position); // gives the index of the correct cursor
            mCursors.get(cursorIndex).moveToPosition(position - getCursorFirstPosition(cursorIndex));
            return mCursors.get(cursorIndex);
        }
    }

    private int getCursorFirstPosition(int cursorIndex) {
        int i = 0;
        while (mIdTypeList.get(i) != cursorIndex)
            i++;
        return i;
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (getItemViewType(position) == 1) {
            // Title view, yaaay
            if (convertView == null) {
	            convertView = mInflater.inflate(mTitleId, null);
            }
            ((TextView) convertView).setText((CharSequence) getItem(position));
            return convertView;
        }  else {
            if (convertView == null) convertView = mInflater.inflate(mContentId, null);
            Cursor whatsit = (Cursor) getItem(position);
            ((TextView) convertView).setText(whatsit.getString(
                    whatsit.getColumnIndex(
                            mColumnsForCursors[mIdTypeList.get(position)])));
	        convertView.setTag(mIdTypeList.get(position));
            return convertView;
        }
    }

    private boolean isTitle(int position) {
        if (mIdTypeList.size() == 0)
            buildIdType();
        return (mIdTypeList.get(position) & TITLE) == TITLE;
    }

	/**
	 *
	 * @param index
	 * @param c
	 */
    public void updateCursorOfType(int index, Cursor c) {
	    if (index >= mTitles.length)
		    throw new IndexOutOfBoundsException("The index " + index + " is out of the range of sections");
        mCursors.set(index, c == null ? new EmptyCursor() : c);
        notifyDataSetChanged();
        buildIdType();
    }

    private void buildIdType() {
        int counter = 0, cursorCounter = mCursors.size(), titleCounter = 0;
        for (int i = 0; i < cursorCounter; i++) {
            mIdTypeList.add(counter, TITLE + titleCounter);
            counter++;
            titleCounter++;
            int thisOne = mCursors.get(i).getCount();
            for (int j = 0; j < thisOne; j++) {
                mIdTypeList.add(counter, i);
                counter++;
            }
        }
        cachedCount = -1;
    }

	public interface OnMultiContentItemClickListener {
		public void onMultiContentItemClicked(int type, String value);
	}

	/**
	 * A handy empty implementation of a Cursor. This is useful because we can
	 * call .getCount() on an apparently "null" Cursor without an exceptionocalpyse.
	 */
	protected static class EmptyCursor implements Cursor {

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

		@Override
		public int getPosition() {
			return 0;
		}

		@Override
		public boolean move(int offset) {
			return true;
		}

		@Override
		public boolean moveToPosition(int position) {
			return true;
		}

		@Override
		public boolean moveToFirst() {
			return true;
		}

		@Override
		public boolean moveToLast() {
			return true;
		}

		@Override
		public boolean moveToNext() {
			return true;
		}

		@Override
		public boolean moveToPrevious() {
			return true;
		}

		@Override
		public boolean isFirst() {
			return true;
		}

		@Override
		public boolean isLast() {
			return true;
		}

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

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

		@Override
		public int getColumnIndex(String columnName) {
			return 0;
		}

		@Override
		public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
			return 0;
		}

		@Override
		public String getColumnName(int columnIndex) {
			return null;
		}

		@Override
		public String[] getColumnNames() {
			return new String[0];
		}

		@Override
		public int getColumnCount() {
			return 0;
		}

		@Override
		public byte[] getBlob(int columnIndex) {
			return new byte[0];
		}

		@Override
		public String getString(int columnIndex) {
			return null;
		}

		@Override
		public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
		}

		@Override
		public short getShort(int columnIndex) {
			return 0;
		}

		@Override
		public int getInt(int columnIndex) {
			return 0;
		}

		@Override
		public long getLong(int columnIndex) {
			return 0;
		}

		@Override
		public float getFloat(int columnIndex) {
			return 0;
		}

		@Override
		public double getDouble(int columnIndex) {
			return 0;
		}

		@Override
		public int getType(int columnIndex) {
			return 0;
		}

		@Override
		public boolean isNull(int columnIndex) {
			return false;
		}

		@Override
		public void deactivate() {
		}

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

		@Override
		public void close() {
		}

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

		@Override
		public void registerContentObserver(ContentObserver observer) {
		}

		@Override
		public void unregisterContentObserver(ContentObserver observer) {
		}

		@Override
		public void registerDataSetObserver(DataSetObserver observer) {
		}

		@Override
		public void unregisterDataSetObserver(DataSetObserver observer) {
		}

		@Override
		public void setNotificationUri(ContentResolver cr, Uri uri) {
		}

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

		@Override
		public Bundle getExtras() {
			return null;
		}

		@Override
		public Bundle respond(Bundle extras) {
			return null;
		}
	}

}