/*
* Copyright 2016 requery.io
*
* 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 io.requery.android;
import android.database.Cursor;
import android.os.Handler;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import io.requery.meta.EntityModel;
import io.requery.meta.Type;
import io.requery.proxy.EntityProxy;
import io.requery.query.Result;
import io.requery.sql.EntityDataStore;
import io.requery.sql.ResultSetIterator;
import io.requery.util.function.Function;
import java.io.Closeable;
import java.sql.SQLException;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* An implementation of {@link android.widget.BaseAdapter} specifically for displaying items from a
* {@link EntityDataStore} query. To use extend this class and implement {@link #performQuery()}
* and {@link #getView(Object, View, ViewGroup)}.
*
* @param <E> entity element type
*
* @author Nikhil Purushe
*/
public abstract class QueryAdapter<E> extends BaseAdapter implements Closeable {
private final Handler handler;
private final Function<E, EntityProxy<E>> proxyProvider;
private ResultSetIterator<E> iterator;
private boolean createdExecutor;
private ExecutorService executor;
private Future<Result<E>> queryFuture;
/**
* Creates a new adapter instance.
*
* @param model database entity model
* @param type entity class type
*/
protected QueryAdapter(EntityModel model, Class<E> type) {
this(model.typeOf(type));
}
/**
* Creates a new adapter instance without any type mapping.
*/
protected QueryAdapter() {
this(null);
}
/**
* Creates a new adapter instance mapped to the given type.
*
* @param type entity type
*/
protected QueryAdapter(Type<E> type) {
proxyProvider = type == null ? null : type.getProxyProvider();
handler = new Handler();
}
/**
* Call this to clean up the underlying result.
*/
@Override
public void close() {
if (queryFuture != null) {
queryFuture.cancel(true);
}
if (iterator != null) {
iterator.close();
iterator = null;
}
}
/**
* Sets the {@link Executor} used for running the query off the main thread.
*
* @param executor ExecutorService to use for the background query.
*/
public void setExecutor(ExecutorService executor) {
if (createdExecutor && this.executor != null) {
this.executor.shutdown();
}
this.executor = executor;
}
/**
* Sets the results the adapter should display.
*
* @param iterator result set iterator
*/
public void setResult(ResultSetIterator<E> iterator) {
close();
this.iterator = iterator;
notifyDataSetChanged();
}
/**
* Schedules the query to be run in the background. After completion
* {@link #setResult(ResultSetIterator)} will be called to update the contents of the adapter.
* Note if an {@link Executor} was not specified one will be created automatically to run the
* query.
*/
public void queryAsync() {
if (this.executor == null) {
this.executor = Executors.newSingleThreadExecutor();
createdExecutor = true;
}
if (queryFuture != null && !queryFuture.isDone()) {
queryFuture.cancel(true);
}
queryFuture = executor.submit(new Callable<Result<E>>() {
@Override
public Result<E> call() {
final Result<E> result = performQuery();
handler.post(new Runnable() {
@Override
public void run() {
setResult((ResultSetIterator<E>) result.iterator());
}
});
return result;
}
});
}
/**
* Implement this method with your query operation. Note this method is executed on a
* background thread not the main thread.
*
* @see #setExecutor(ExecutorService)
*
* @return query result set
*/
public abstract Result<E> performQuery();
@Override
public E getItem(int position) {
if (iterator == null) {
return null;
}
return iterator.get(position);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
E item = getItem(position);
return getView(item, convertView, parent);
}
/**
* Called to display the data at the specified position for the given item. The item is
* retrieved from the result iterator.
*
* @param item entity element to bind to the view
* @param convertView view being recycled
* @param parent parent view
*/
public abstract View getView(E item, View convertView, ViewGroup parent);
@Override
public long getItemId(int position) {
E item = iterator.get(position);
Object key = null;
if (proxyProvider != null) {
EntityProxy<E> proxy = proxyProvider.apply(item);
key = proxy.key();
}
return key == null ? item.hashCode() : key.hashCode();
}
@Override
public int getCount() {
if (iterator == null) {
return 0;
}
try {
Cursor cursor = iterator.unwrap(Cursor.class);
return cursor.getCount();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}