package com.conference.app.lib.ui.adapter;
/***
* Copyright (c) 2008-2009 CommonsWare, LLC Portions (c) 2009 Google, Inc.
*
* 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.
*/
import java.util.concurrent.atomic.AtomicBoolean;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListAdapter;
import com.commonsware.cwac.adapter.AdapterWrapper;
/**
* Adapter that assists another adapter in appearing endless. For example, this could be used for an adapter being
* filled by a set of Web service calls, where each call returns a "page" of data.
*
* Subclasses need to be able to return, via getPendingView() a row that can serve as both a placeholder while more data
* is being appended.
*
* The actual logic for loading new data should be done in appendInBackground(). This method, as the name suggests, is
* run in a background thread. It should return true if there might be more data, false otherwise.
*
* If your situation is such that you will not know if there is more data until you do some work (e.g., make another Web
* service call), it is up to you to do something useful with that row returned by getPendingView() to let the user know
* you are out of data, plus return false from that final call to appendInBackground().
*/
public abstract class AbstractEndlessAdapter extends AdapterWrapper {
protected abstract boolean cacheInBackground() throws Exception;
protected abstract void appendCachedData();
private View pendingView = null;
private AtomicBoolean keepOnAppending = new AtomicBoolean(true);
private Context context;
private int pendingResource = -1;
/**
* Constructor wrapping a supplied ListAdapter
*/
public AbstractEndlessAdapter(final ListAdapter wrapped) {
super(wrapped);
}
/**
* Constructor wrapping a supplied ListAdapter and providing a id for a pending view.
*
* @param context
* @param wrapped
* @param pendingResource
*/
public AbstractEndlessAdapter(final Context context, final ListAdapter wrapped, final int pendingResource) {
super(wrapped);
this.context = context;
this.pendingResource = pendingResource;
}
public void setKeepOnAppending(final boolean flag) {
keepOnAppending.set(flag);
}
/**
* How many items are in the data set represented by this Adapter.
*/
@Override
public int getCount() {
if (keepOnAppending.get()) {
return (super.getCount() + 1); // one more for "pending"
}
return (super.getCount());
}
/**
* Masks ViewType so the AdapterView replaces the "Pending" row when new data is loaded.
*/
public int getItemViewType(final int position) {
if (position == getWrappedAdapter().getCount()) {
return (IGNORE_ITEM_VIEW_TYPE);
}
return (super.getItemViewType(position));
}
/**
* Masks ViewType so the AdapterView replaces the "Pending" row when new data is loaded.
*
* @see #getItemViewType(int)
*/
public int getViewTypeCount() {
return (super.getViewTypeCount() + 1);
}
/**
* Get a View that displays the data at the specified position in the data set. In this case, if we are at the end
* of the list and we are still in append mode, we ask for a pending view and return it, plus kick off the
* background task to append more data to the wrapped adapter.
*
* @param position
* Position of the item whose data we want
* @param convertView
* View to recycle, if not null
* @param parent
* ViewGroup containing the returned View
*/
@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
if (position == super.getCount() && keepOnAppending.get()) {
if (pendingView == null) {
pendingView = getPendingView(parent);
new AppendTask().execute();
}
return (pendingView);
}
return (super.getView(position, convertView, parent));
}
/**
* Called if cacheInBackground() raises a runtime exception, to allow the UI to deal with the exception on the main
* application thread.
*
* @param pendingView
* View representing the pending row
* @param e
* Exception that was raised by cacheInBackground()
* @return true if should allow retrying appending new data, false otherwise
*/
protected boolean onException(final View pendingView, final Exception e) {
Log.e("EndlessAdapter", "Exception in cacheInBackground()", e);
return (false);
}
/**
* A background task that will be run when there is a need to append more data. Mostly, this code delegates to the
* subclass, to append the data in the background thread and rebind the pending view once that is done.
*/
class AppendTask extends AsyncTask<Void, Void, Exception> {
@Override
protected Exception doInBackground(final Void... params) {
Exception result = null;
try {
keepOnAppending.set(cacheInBackground());
} catch (Exception e) {
result = e;
}
return (result);
}
@Override
protected void onPostExecute(final Exception e) {
if (e == null) {
appendCachedData();
} else {
keepOnAppending.set(onException(pendingView, e));
}
pendingView = null;
notifyDataSetChanged();
}
}
/**
* Inflates pending view using the pendingResource ID passed into the constructor
*
* @param parent
* @return inflated pending view, or null if the context passed into the pending view constructor was null.
*/
protected View getPendingView(final ViewGroup parent) {
if (context != null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return inflater.inflate(pendingResource, parent, false);
}
throw new RuntimeException(
"You must either override getPendingView() or supply a pending View resource via the constructor");
}
/**
* Getter method for the Context being held by the adapter
*
* @return Context
*/
protected Context getContext() {
return (context);
}
}