Skip to content

Instantly share code, notes, and snippets.

@amlcurran
Last active December 11, 2015 03:38
Show Gist options
  • Save amlcurran/4539165 to your computer and use it in GitHub Desktop.
Save amlcurran/4539165 to your computer and use it in GitHub Desktop.
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;
}
}
}
@imminent
Copy link

Since it is handling multiple content, shouldn't it be able to support different views for the content? The view type counts can handle most of the work needed to do this. I'm specifically thinking of defining a view per Cursor, and then the Adapter makes sure to match items from a given Cursor to the view for that Cursor.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment