package com.datdo.mobilib.base; import java.util.ArrayList; import java.util.Collection; import java.util.List; import com.datdo.mobilib.util.MblUtils; import junit.framework.Assert; import android.view.LayoutInflater; import android.widget.BaseAdapter; import android.widget.ListView; /** * <pre> * An extension of {@link BaseAdapter}. * Make all data changes and calls of {@link #notifyDataSetChanged()} synchronized on main thread to prevent concurrency problems. * </pre> * @param <T> class of object bound with an item of {@link ListView} */ public abstract class MblBaseAdapter<T> extends BaseAdapter { @SuppressWarnings("serial") private final List<T> mData = new ArrayList<T>() { @Override public void clear() { Assert.assertTrue(MblUtils.isMainThread()); super.clear(); } @Override public void add(int index, T object) { Assert.assertTrue(MblUtils.isMainThread()); super.add(index, object); } @Override public boolean add(T object) { Assert.assertTrue(MblUtils.isMainThread()); return super.add(object); } @Override public boolean addAll(Collection<? extends T> collection) { Assert.assertTrue(MblUtils.isMainThread()); return super.addAll(collection); } @Override public boolean addAll(int index, Collection<? extends T> collection) { Assert.assertTrue(MblUtils.isMainThread()); return super.addAll(index, collection); } @Override public T remove(int index) { Assert.assertTrue(MblUtils.isMainThread()); return super.remove(index); } @Override public boolean remove(Object object) { Assert.assertTrue(MblUtils.isMainThread()); return super.remove(object); } @Override public boolean removeAll(Collection<?> collection) { Assert.assertTrue(MblUtils.isMainThread()); return super.removeAll(collection); } @Override protected void removeRange(int fromIndex, int toIndex) { Assert.assertTrue(MblUtils.isMainThread()); super.removeRange(fromIndex, toIndex); } }; private LayoutInflater mLayoutInflater; /** * <pre> * Get LayoutInflater instance which is mandatory for most Adapter. * </pre> */ protected LayoutInflater getLayoutInflater() { if (mLayoutInflater == null) { mLayoutInflater = LayoutInflater.from(MblUtils.getCurrentContext()); } return mLayoutInflater; } /** * <pre> * Subclasses use this method to get internal data objects. * </pre> * @return list of internal data objects. */ protected List<T> getData() { return mData; } @Override public void notifyDataSetChanged() { MblUtils.executeOnMainThread(new Runnable() { @Override public void run() { MblBaseAdapter.super.notifyDataSetChanged(); } }); } @Override public int getCount() { return mData.size(); } @Override public Object getItem(int pos) { if (pos < mData.size()) { return mData.get(pos); } else { return null; } } @Override public long getItemId(int pos) { return pos; } /** * <pre> * Subclasses is strongly recommended to use this method when it wants to change internal data. * * Here is an example: * <code> * public void appendData(Object newItem) { * changeDataSafely(new Runnable() { * {@literal @}Override * public void run() { * getData().add(newItem); * notifyDataSetChanged(); * } * }); * } * </code> * </pre> * @param action wraps all changes on internal data */ protected void changeDataSafely(final Runnable action) { MblUtils.executeOnMainThread(new Runnable() { @Override public void run() { synchronized (mData) { action.run(); } } }); } /** * <pre> * Discard all current data and replace them by new ones. * Also refresh UI automatically. * </pre> * @param data new data */ public void changeData(final List<T> data) { changeDataSafely(new Runnable() { @Override public void run() { getData().clear(); if (data != null) { getData().addAll(data); } notifyDataSetChanged(); } }); } /** * <pre> * Discard all current data and replace them by new ones. * Also refresh UI automatically. * </pre> * @param data new data */ public void changeData(final T[] data) { changeDataSafely(new Runnable() { @Override public void run() { getData().clear(); if (data != null) { for (T d : data) { getData().add(d); } } notifyDataSetChanged(); } }); } }