com.gelakinetic.mtgfam.helpers.AutocompleteCursorAdapter.java Source code

Java tutorial

Introduction

Here is the source code for com.gelakinetic.mtgfam.helpers.AutocompleteCursorAdapter.java

Source

/**
 * Copyright 2011 Adam Feinstein
 * <p/>
 * This file is part of MTG Familiar.
 * <p/>
 * MTG Familiar is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * <p/>
 * MTG Familiar is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * <p/>
 * You should have received a copy of the GNU General Public License
 * along with MTG Familiar.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.gelakinetic.mtgfam.helpers;

import android.app.SearchManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AutoCompleteTextView;
import android.widget.SimpleCursorAdapter;

import com.gelakinetic.mtgfam.R;
import com.gelakinetic.mtgfam.fragments.FamiliarFragment;
import com.gelakinetic.mtgfam.helpers.database.CardDbAdapter;
import com.gelakinetic.mtgfam.helpers.database.CardSearchProvider;

import org.jetbrains.annotations.NotNull;

/**
 * This cursor adapter provides suggestions for card names directly from the database
 */
public class AutocompleteCursorAdapter extends SimpleCursorAdapter
        implements LoaderManager.LoaderCallbacks<Cursor> {

    private static final String[] CARD_NAME_PROJECTION = new String[] { CardDbAdapter.KEY_ID,
            CardDbAdapter.KEY_NAME, };
    private static final Uri SEARCH_URI = Uri
            .parse("content://" + CardSearchProvider.AUTHORITY + "/" + SearchManager.SUGGEST_URI_PATH_QUERY);
    private final String[] mAutocompleteFilter = new String[1];

    private final FamiliarFragment mFragment;

    /**
     * Standard constructor.
     *
     * @param context  The context where the ListView associated with this SimpleListItemFactory is running
     * @param from     A list of column names representing the data to bind to the UI. Can be null if the cursor is not
     *                 available yet.
     * @param to       The views that should display column in the "from" parameter. These should all be TextViews. The
     *                 first N views in this list are given the values of the first N columns in the from parameter.
     *                 Can be null if the cursor is not available yet.
     * @param textView The text view which we are watching for changes
     */
    public AutocompleteCursorAdapter(FamiliarFragment context, String[] from, int[] to,
            AutoCompleteTextView textView, boolean showArrowhead) {
        super(context.getActivity(), showArrowhead ? R.layout.list_item_1_arrowhead : R.layout.list_item_1, null,
                from, to, 0);
        mFragment = context;
        mFragment.getLoaderManager().initLoader(0, null, this);
        textView.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
                /* Don't care */
            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
                /* Don't care */
            }

            @Override
            public void afterTextChanged(Editable editable) {
                /* Preform a query */
                mAutocompleteFilter[0] = String.valueOf(editable);
                mFragment.getLoaderManager().restartLoader(0, null, AutocompleteCursorAdapter.this);
            }
        });
    }

    /**
     * Instantiate and return a new Loader for the given ID.
     *
     * @param id   The ID whose loader is to be created.
     * @param args Any arguments supplied by the caller.
     * @return Return a new Loader instance that is ready to start loading.
     */
    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        /* Now create and return a CursorLoader that will take care of creating a Cursor for the data being displayed.
         */
        String select = "(" + CardDbAdapter.KEY_NAME + ")";
        return new CursorLoader(mFragment.getActivity(), SEARCH_URI, CARD_NAME_PROJECTION, select,
                mAutocompleteFilter, CardDbAdapter.KEY_NAME + " COLLATE LOCALIZED ASC");
    }

    /**
     * Called when a previously created loader has finished its load.
     *
     * @param loader The Loader that has finished.
     * @param data   The data generated by the Loader.
     */
    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        try {
            Cursor old;
            if (data != null && data.isClosed()) {
                old = this.swapCursor(null);

            } else {
                old = this.swapCursor(data);
            }
            if (old != null) {
                old.close();
            }
        } catch (NullPointerException e) {
            /* eat it */
        }
    }

    /**
     * Called when a previously created loader is being reset, and thus making its data unavailable.
     *
     * @param loader The Loader that is being reset.
     */
    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        this.swapCursor(null);
    }

    /**
     * Converts the cursor into a CharSequence.
     *
     * @param cursor the cursor to convert to a CharSequence
     * @return a CharSequence representing the value
     */
    @Override
    public CharSequence convertToString(@NotNull Cursor cursor) {
        try {
            return cursor.getString(cursor.getColumnIndex(CardDbAdapter.KEY_NAME));
        } catch (Exception e) {
            /* If there is any problem, return the empty string */
            return "";
        }
    }

    /**
     * Get the data item associated with the specified position in the data set.
     * If there is an exception, safely abandon the cursor
     *
     * @param position Position of the item whose data we want within the adapter's data set.
     * @return The data at the position, or null
     */
    @Override
    public Object getItem(int position) {
        try {
            return super.getItem(position);
        } catch (Exception e) {
            this.swapCursor(null);
            return null;
        }
    }

    /**
     * Get the row id associated with the specified position in the list.
     * If there is an exception, safely abandon the cursor
     *
     * @param position The position of the item within the adapter's data set whose row id we want.
     * @return The id of the item at the specified position.
     */
    @Override
    public long getItemId(int position) {
        try {
            return super.getItemId(position);
        } catch (Exception e) {
            this.swapCursor(null);
            return 0;
        }
    }

    /**
     * Get a View that displays the data at the specified position in the data set. You can either
     * create a View manually or inflate it from an XML layout file. When the View is inflated, the
     * parent View (GridView, ListView...) will apply default layout parameters unless you use
     * inflate(int, android.view.ViewGroup, boolean) to specify a root view and to prevent
     * attachment to the root.
     * If there is an exception, safely abandon the cursor
     *
     * @param position    The position of the item within the adapter's data set of the item whose
     *                    view we want.
     * @param convertView The old view to reuse, if possible. Note: You should check that this view
     *                    is non-null and of an appropriate type before using. If it is not possible
     *                    to convert this view to display the correct data, this method can create a
     *                    new view. Heterogeneous lists can specify their number of view types, so
     *                    that this View is always of the right type (see getViewTypeCount() and
     *                    getItemViewType(int)).
     * @param parent      The parent that this view will eventually be attached to
     * @return A View corresponding to the data at the specified position.
     */
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        try {
            return super.getView(position, convertView, parent);
        } catch (Exception e) {
            /* Now that prior failures swap in a null cursor, this should never be called */
            this.swapCursor(null);
            if (convertView != null) {
                return convertView;
            } else {
                return new View(mFragment.getActivity());
            }
        }
    }
}