com.sawyer.advadapters.widget.NFJSONArrayAdapter.java Source code

Java tutorial

Introduction

Here is the source code for com.sawyer.advadapters.widget.NFJSONArrayAdapter.java

Source

/**
 * Copyright 2014 Jay Soyer
 *
 * 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.sawyer.advadapters.widget;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import java.util.Collection;

/**
 * A non-filterable custom abstract {@link BaseAdapter} that is backed by a {@link JSONArray} of
 * arbitrary objects. By default this class delegates view generation to subclasses.
 * <p/>
 * Designed to be a flexible and customizable solution for using JSONArray with an adapter but
 * without the filtering mechanism. As a result, there is no need for {@code synchronized} blocks
 * which may help those worried about performance. It exposes most of the JSONArray methods and
 * conveniently passes along a layout inflater for view creation. Keep in mind JSONArray itself has
 * limited capabilities which restricts what this adapter can do.
 * <p/>
 * If filtering is required, it's strongly recommended to use the {@link JSONAdapter} instead.
 */
public abstract class NFJSONArrayAdapter extends BaseAdapter {
    /** LayoutInflater created from the constructing context */
    private LayoutInflater mInflater;
    /** Activity Context used to construct this adapter * */
    private Context mContext;
    /**
     * Contains the list of objects that represent the visible data of the adapter. It's contents
     * will change as filtering occurs. All methods retrieving data about the adapter will always do
     * so from this list.
     */
    private JSONArray mObjects;
    /**
     * Indicates whether or not {@link #notifyDataSetChanged()} must be called whenever {@link
     * #mObjects} is modified.
     */
    private boolean mNotifyOnChange = true;

    /**
     * Constructor
     *
     * @param activity Context used for inflating views
     */
    public NFJSONArrayAdapter(Context activity) {
        init(activity, new JSONArray());
    }

    /**
     * Constructor
     *
     * @param activity Context used for inflating views
     * @param readFrom a tokener whose nextValue() method will yield a {@code JSONArray} to be
     *                 stored in the adapter.
     *
     * @throws org.json.JSONException if the parse fails or doesn't yield a {@code JSONArray}.
     */
    public NFJSONArrayAdapter(Context activity, JSONTokener readFrom) throws JSONException {
        init(activity, new JSONArray(readFrom));
    }

    /**
     * * Constructor
     *
     * @param activity Context used for inflating views
     * @param json     a JSON-encoded string containing an array to be stored in the adapter
     *
     * @throws org.json.JSONException if the parse fails or doesn't yield a {@code JSONArray}.
     */
    public NFJSONArrayAdapter(Context activity, String json) throws JSONException {
        init(activity, new JSONArray(json));
    }

    public NFJSONArrayAdapter(Context activity, JSONArray array) {
        init(activity, generateCopy(array));
    }

    /**
     * Constructor
     *
     * @param activity Context used for inflating views
     * @param items    The items to represent within the adapter.
     */
    public NFJSONArrayAdapter(Context activity, Collection items) {
        init(activity, new JSONArray(items));
    }

    /**
     * Creates a new {@code JSONArray} with values from another. Adds backward support as this only
     * exists in API19.
     */
    private static JSONArray generateCopy(JSONArray array) {
        JSONArray copy = new JSONArray();
        for (int i = 0; i < array.length(); ++i) {
            Object object = array.opt(i);
            if (object != null) {
                copy.put(object);
            }
        }
        return copy;
    }

    /**
     * Adds the specified items at the end of the adapter. May not be NaNs or infinities which will
     * cause the adapter to be in an inconsistent state.
     *
     * @param item The item to add at the end of the adapter.
     */
    public void add(Object item) {
        mObjects.put(item);
        if (mNotifyOnChange)
            notifyDataSetChanged();
    }

    /**
     * Adds the specified items at the end of the adapter. May not be NaNs or infinities.
     *
     * @param item The item to add at the end of the adapter.
     *
     * @throws org.json.JSONException If item is NaN or infinity
     */
    public void add(double item) throws JSONException {
        mObjects.put(item);
        if (mNotifyOnChange)
            notifyDataSetChanged();
    }

    /**
     * Adds the specified items at the end of the adapter
     *
     * @param item The item to add at the end of the adapter.
     */
    public void add(long item) {
        mObjects.put(item);
        if (mNotifyOnChange)
            notifyDataSetChanged();
    }

    /**
     * Adds the specified items at the end of the adapter.
     *
     * @param item The item to add at the end of the adapter.
     */
    public void add(boolean item) {
        mObjects.put(item);
        if (mNotifyOnChange)
            notifyDataSetChanged();
    }

    /**
     * Adds the specified items at the end of the adapter.
     *
     * @param item The item to add at the end of the adapter.
     */
    public void add(int item) {
        mObjects.put(item);
        if (mNotifyOnChange)
            notifyDataSetChanged();
    }

    /**
     * Adds the specified JSONArray at the end of the adapter. May not contain NaNs or infinities
     * which will cause the adapter to be in an inconsistent state.
     *
     * @param items The JSONArray to add at the end of the adapter.
     */
    public void addAll(JSONArray items) {
        for (int index = 0; index < items.length(); ++index) {
            mObjects.put(items.opt(index));
        }
        if (mNotifyOnChange)
            notifyDataSetChanged();
    }

    /**
     * Remove all elements from the adapter.
     */
    public void clear() {
        mObjects = new JSONArray();
        if (mNotifyOnChange)
            notifyDataSetChanged();
    }

    /**
     * @return The Context associated with this adapter.
     */
    public Context getContext() {
        return mContext;
    }

    @Override
    public int getCount() {
        return mObjects.length();
    }

    /**
     * <p>Get a {@link android.view.View} that displays in the drop down popup the data at the
     * specified position in the data set.</p>
     *
     * @param inflater    the LayoutInflater object that can be used to inflate each view.
     * @param position    index 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.
     * @param parent      the parent that this view will eventually be attached to
     *
     * @return a {@link android.view.View} corresponding to the data at the specified position.
     */
    public View getDropDownView(LayoutInflater inflater, int position, View convertView, ViewGroup parent) {
        return getView(inflater, position, convertView, parent);
    }

    @Override
    public final View getDropDownView(int position, View convertView, ViewGroup parent) {
        return getDropDownView(mInflater, position, convertView, parent);
    }

    @Override
    public Object getItem(int position) {
        Object object = mObjects.opt(position);
        if (object == null) {
            //A pain but can't add throws to this overrided method
            if (position < 0 || position >= mObjects.length()) {
                throw new IndexOutOfBoundsException();
            } else {
                throw new NullPointerException();
            }
        }
        return object;
    }

    /**
     * Gets the boolean data item associated with the specified position in the data set.
     *
     * @param position Position of the item whose data we want within the adapter's data set.
     *
     * @return The data at the specified position as a boolean.
     *
     * @throws org.json.JSONException If the value at position doesn't exit or cannot be coerced to
     *                                a boolean.
     */
    public boolean getItemBoolean(int position) throws JSONException {
        return mObjects.getBoolean(position);
    }

    /**
     * Gets the double data item associated with the specified position in the data set.
     *
     * @param position Position of the item whose data we want within the adapter's data set.
     *
     * @return The data at the specified position as a double.
     *
     * @throws org.json.JSONException If the value at position doesn't exit or cannot be coerced to
     *                                a double.
     */
    public double getItemDouble(int position) throws JSONException {
        return mObjects.getDouble(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    /**
     * Gets the int data item associated with the specified position in the data set.
     *
     * @param position Position of the item whose data we want within the adapter's data set.
     *
     * @return The data at the specified position as a int.
     *
     * @throws org.json.JSONException If the value at position doesn't exit or cannot be coerced to
     *                                a int.
     */
    public int getItemInt(int position) throws JSONException {
        return mObjects.getInt(position);
    }

    /**
     * Gets the JSONArray data item associated with the specified position in the data set.
     *
     * @param position Position of the item whose data we want within the adapter's data set.
     *
     * @return The data at the specified position as a JSONArray.
     *
     * @throws org.json.JSONException If the value at position doesn't exit or is not a JSONArray.
     */
    public JSONArray getItemJSONArray(int position) throws JSONException {
        return mObjects.getJSONArray(position);
    }

    /**
     * Gets the JSONObject data item associated with the specified position in the data set.
     *
     * @param position Position of the item whose data we want within the adapter's data set.
     *
     * @return The data at the specified position as a JSONObject.
     *
     * @throws org.json.JSONException If the value at position doesn't exit or is not a JSONObject.
     */
    public JSONObject getItemJSONObject(int position) throws JSONException {
        return mObjects.getJSONObject(position);
    }

    /**
     * Gets the long data item associated with the specified position in the data set.
     *
     * @param position Position of the item whose data we want within the adapter's data set.
     *
     * @return The data at the specified position as a long.
     *
     * @throws org.json.JSONException If the value at position doesn't exit or cannot be coerced to
     *                                a long.
     */
    public long getItemLong(int position) throws JSONException {
        return mObjects.getLong(position);
    }

    /**
     * Gets the String data item associated with the specified position in the data set.
     *
     * @param position Position of the item whose data we want within the adapter's data set.
     *
     * @return The data at the specified position as a String.
     *
     * @throws org.json.JSONException If no such value exists.
     */
    public String getItemString(int position) throws JSONException {
        return mObjects.getString(position);
    }

    /**
     * @return The original list of items stored within the Adapter
     */
    public JSONArray getJSONArray() {
        return generateCopy(mObjects);
    }

    /**
     * Resets the adapter to store a new JSONArray of items. Convenient way of calling {@link
     * #clear()}, then {@link #addAll(org.json.JSONArray)} without having to worry about an extra
     * {@link #notifyDataSetChanged()} invoked in between. Will repeat the last filtering request if
     * invoked while filtered results are being displayed.
     *
     * @param items New JSONArray of items to store within the adapter.
     */
    public void setJSONArray(JSONArray items) {
        mObjects = generateCopy(items);
        if (mNotifyOnChange)
            notifyDataSetChanged();
    }

    /**
     * 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
     * {@link android.view.LayoutInflater#inflate(int, android.view.ViewGroup, boolean)} to specify
     * a root view and to prevent attachment to the root.
     *
     * @param inflater    the LayoutInflater object that can be used to inflate each view.
     * @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 {@link
     *                    #getViewTypeCount()} and {@link #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.
     */
    public abstract View getView(LayoutInflater inflater, int position, View convertView, ViewGroup parent);

    @Override
    public final View getView(int position, View convertView, ViewGroup parent) {
        return this.getView(mInflater, position, convertView, parent);
    }

    private void init(Context context, JSONArray objects) {
        mInflater = LayoutInflater.from(context);
        mContext = context;
        mObjects = objects;
    }

    /**
     * @return True if this adapter has no value at position, or if it's value is the {@code null}
     * reference or {@link org.json.JSONObject#NULL NULL}.
     */
    public boolean isNull(int position) {
        return mObjects.isNull(position);
    }

    @Override
    public void notifyDataSetChanged() {
        super.notifyDataSetChanged();
        mNotifyOnChange = true;
    }

    /**
     * Gets the data item associated with the specified position in the adapter or null if there is
     * no value at position.
     *
     * @param position Position of the item whose data we want within the adapter's data set.
     *
     * @return The data at the specified position or null.
     */
    public Object optItem(int position) {
        return mObjects.opt(position);
    }

    /**
     * Gets the boolean data item associated with the specified position in the adapter or null if
     * there is no value at position.
     *
     * @param position Position of the item whose data we want within the adapter's data set.
     *
     * @return The boolean data at the specified position or null.
     */
    public boolean optItemBoolean(int position) {
        return mObjects.optBoolean(position);
    }

    /**
     * Gets the boolean data item associated with the specified position in the adapter or null if
     * there is no value at position.
     *
     * @param position Position of the item whose data we want within the adapter's data set.
     * @param fallback Value to return if no data is found.
     *
     * @return The boolean data at the specified position or otherwise the fallback.
     */
    public boolean optItemBoolean(int position, boolean fallback) {
        return mObjects.optBoolean(position, fallback);
    }

    /**
     * Gets the double data item associated with the specified position in the adapter or null if
     * there is no value at position.
     *
     * @param position Position of the item whose data we want within the adapter's data set.
     *
     * @return The double data at the specified position or null.
     */
    public double optItemDouble(int position) {
        return mObjects.optDouble(position);
    }

    /**
     * Gets the double data item associated with the specified position in the adapter or null if
     * there is no value at position.
     *
     * @param position Position of the item whose data we want within the adapter's data set.
     * @param fallback Value to return if no data is found.
     *
     * @return The double data at the specified position or otherwise the fallback.
     */
    public double optItemDouble(int position, double fallback) {
        return mObjects.optDouble(position, fallback);
    }

    /**
     * Gets the int data item associated with the specified position in the adapter or null if there
     * is no value at position.
     *
     * @param position Position of the item whose data we want within the adapter's data set.
     *
     * @return The int data at the specified position or null.
     */
    public int optItemInt(int position) {
        return mObjects.optInt(position);
    }

    /**
     * Gets the int data item associated with the specified position in the adapter or null if there
     * is no value at position.
     *
     * @param position Position of the item whose data we want within the adapter's data set.
     * @param fallback Value to return if no data is found.
     *
     * @return The int data at the specified position or otherwise the fallback.
     */
    public int optItemInt(int position, int fallback) {
        return mObjects.optInt(position, fallback);
    }

    /**
     * Gets the JSONArray data item associated with the specified position in the adapter or null if
     * there is no value at position.
     *
     * @param position Position of the item whose data we want within the adapter's data set.
     *
     * @return The JSONArray data at the specified position or null.
     */
    public JSONArray optItemJSONArray(int position) {
        return mObjects.optJSONArray(position);
    }

    /**
     * Gets the JSONObject data item associated with the specified position in the adapter or null
     * if there is no value at position.
     *
     * @param position Position of the item whose data we want within the adapter's data set.
     *
     * @return The JSONObject data at the specified position or null.
     */
    public JSONObject optItemJSONObject(int position) {
        return mObjects.optJSONObject(position);
    }

    /**
     * Gets the long data item associated with the specified position in the adapter or null if
     * there is no value at position.
     *
     * @param position Position of the item whose data we want within the adapter's data set.
     *
     * @return The long data at the specified position or null.
     */
    public long optItemLong(int position) {
        return mObjects.optLong(position);
    }

    /**
     * Gets the long data item associated with the specified position in the adapter or null if
     * there is no value at position.
     *
     * @param position Position of the item whose data we want within the adapter's data set.
     * @param fallback Value to return if no data is found.
     *
     * @return The long data at the specified position or otherwise the fallback.
     */
    public long optItemLong(int position, long fallback) {
        return mObjects.optLong(position, fallback);
    }

    /**
     * Gets the String data item associated with the specified position in the adapter or null if
     * there is no value at position.
     *
     * @param position Position of the item whose data we want within the adapter's data set.
     *
     * @return The String data at the specified position or null.
     */
    public String optItemString(int position) {
        return mObjects.optString(position);
    }

    /**
     * Gets the String data item associated with the specified position in the adapter or null if
     * there is no value at position.
     *
     * @param position Position of the item whose data we want within the adapter's data set.
     * @param fallback Value to return if no data is found.
     *
     * @return The String data at the specified position or otherwise the fallback.
     */
    public String optItemString(int position, String fallback) {
        return mObjects.optString(position, fallback);
    }

    /**
     * Control whether methods that change the list ({@link #add}, {@link #clear}) automatically
     * call {@link #notifyDataSetChanged}.  If set to false, caller must manually call
     * notifyDataSetChanged() to have the changes reflected in the attached view.
     * <p/>
     * The default is true, and calling notifyDataSetChanged() resets the flag to true.
     *
     * @param notifyOnChange if true, modifications to the list will automatically call {@link
     *                       #notifyDataSetChanged}
     */
    public void setNotifyOnChange(boolean notifyOnChange) {
        mNotifyOnChange = notifyOnChange;
    }

    /**
     * Updates the item at the specified position in the adapter with the specified item. This
     * operation does not change the size of the adapter.
     *
     * @param position The location at which to put the specified item
     * @param item     The new item to replace with the old
     *
     * @throws org.json.JSONException If item is NaN or infinity
     */
    public void update(int position, Object item) throws JSONException {
        mObjects.put(position, item);
        if (mNotifyOnChange)
            notifyDataSetChanged();
    }
}