SherlockFragmentActivity多ListFragments和SQLite光标战斗(

2019-09-18 09:39发布

我使用的扩展SherlockFragmentActivity的活动,但有3个标签。 在3个标签是实现LoaderManager.LoaderCallbacks ListFragments。 该活动的onCreate方法加载像这样的标签

bar.addTab(bar.newTab()
    .setTag("venues_list")
    .setText(getString(R.string.list_venues_header))
    .setTabListener(new TabListener<VenueListFragment>(
        this, getString(R.string.list_invites_header), VenueListFragment.class, null)));

// I do the EXACT same thing for the other two tabs, using their respective ListFragments

if (savedInstanceState != null) {
    bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
}

那我加载的卡口插入布局非常简单:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

</LinearLayout>

每个标签有一个类,等同于这一点,只是叫不同的东西:

import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.support.v4.widget.SimpleCursorAdapter;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.Toast;

import com.actionbarsherlock.app.SherlockListFragment;
import com.lateral.oursvp.R;
import com.lateral.oursvp.database.SimpleCursorLoader;
import com.lateral.oursvp.database.VenuesDataSource;

/**
 * @author rabbott
 *
 */
public class VenueListFragment extends SherlockListFragment implements LoaderManager.LoaderCallbacks<Cursor> {  
    SharedPreferences appPreferences;

    private CursorAdapter cursorAdapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // bind the columns of the cursor to the list
        String[] from = new String[] { VenuesDataSource.KEY_NAME, VenuesDataSource.KEY_DESCRIPTION };
        int[] to = new int[] { R.id.list_item_title, R.id.list_item_subtitle };

        cursorAdapter = new SimpleCursorAdapter( getActivity(), R.layout.list_item, null, from, to, 0);

        // retrieve the listview to populate
        ListView lv = (ListView) getActivity().findViewById(android.R.id.list);

        // set the adapter on the listview
        lv.setAdapter(cursorAdapter);

        // click event for each row of the list
        lv.setOnItemClickListener(new OnItemClickListener() {

            public void onItemClick(AdapterView<?> arg0, View view,
                    int position, long id) {
                Cursor cursor = cursorAdapter.getCursor();
                cursor.moveToPosition(position);

                Toast.makeText(getActivity(), "Tapped row " + position + "!", Toast.LENGTH_SHORT).show();
            }
        });

        // Start out with a progress indicator.
        setListShown(false);

        // load the data
        getActivity().getSupportLoaderManager().initLoader(0, null, this);
    }

    public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
        return new VenueCursorLoader(getActivity());
    }

    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        cursorAdapter.swapCursor(cursor);

        // the list should now be shown
        if (isResumed()) {
            setListShown(true);
        } else {
            setListShownNoAnimation(true);
        }
    }

    public void onLoaderReset(Loader<Cursor> loader) {
        cursorAdapter.swapCursor(null);
    }

    public static final class VenueCursorLoader extends SimpleCursorLoader {

        Context mContext;

        public VenueCursorLoader(Context context) {
            super(context);

            mContext = context;
        }

        @Override
        public Cursor loadInBackground() {
            VenuesDataSource datasource = new VenuesDataSource(mContext);

            return datasource.getAllVenues(((EventActivity) mContext).getEventId());
        }

    }
}

它使用SimpleCursorLoaded定义如下:

import android.content.Context;
import android.database.Cursor;
import android.support.v4.content.AsyncTaskLoader;

public abstract class SimpleCursorLoader extends AsyncTaskLoader<Cursor> {
    private Cursor mCursor;

    public SimpleCursorLoader(Context context) {
        super(context);
    }

    /* Runs on a worker thread */
    @Override
    public abstract Cursor loadInBackground();

    /* Runs on the UI thread */
    @Override
    public void deliverResult(Cursor cursor) {
        if (isReset()) {
            // An async query came in while the loader is stopped
            if (cursor != null) {
                cursor.close();
            }
            return;
        }
        Cursor oldCursor = mCursor;
        mCursor = cursor;

        if (isStarted()) {
            super.deliverResult(cursor);
        }

        if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
            oldCursor.close();
        }
    }

    /**
     * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
     * will be called on the UI thread. If a previous load has been completed and is still valid
     * the result may be passed to the callbacks immediately.
     * <p/>
     * Must be called from the UI thread
     */
    @Override
    protected void onStartLoading() {
        if (mCursor != null) {
            deliverResult(mCursor);
        }
        if (takeContentChanged() || mCursor == null) {
            forceLoad();
        }
    }

    /**
     * Must be called from the UI thread
     */
    @Override
    protected void onStopLoading() {
        // Attempt to cancel the current load task if possible.
        cancelLoad();
    }

    @Override
    public void onCanceled(Cursor cursor) {
        if (cursor != null && !cursor.isClosed()) {
            cursor.close();
        }
    }

    @Override
    protected void onReset() {
        super.onReset();

        // Ensure the loader is stopped
        onStopLoading();

        if (mCursor != null && !mCursor.isClosed()) {
            mCursor.close();
        }
        mCursor = null;
    }
}

我遇到的问题是,当我选择说..第二个选项卡(第三个选项卡不相同),它会尝试加载光标,但是当它试图填充list_items,我得到一个错误说

E/AndroidRuntime(2055): java.lang.IllegalArgumentException: column 'GIVEN_VARIABLE' does not exist

设置我发现光标它试图使用从第一个选项卡,里面装上充气微细光标,但显然是没有得到关闭,显然是想再次使用,即使我的几个破发点后(想)我送它getAllVenues不同的光标()

编辑:这是最后的地方堆栈跟踪派我当我设置一些断点,我可以看到,这里正在提供的光标,是从第一标签中的光标,而不是场地标签..

编辑:VenuesDataSource代码显示getAllVenues方法

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

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.util.Log;

/**
 * @author rabbott
 *
 */
public class VenuesDataSource extends appSQLiteHelper {

    public static final String TABLE_NAME               = "venues";

    // venue columns
    public static final String KEY_NAME                 = "name";
    public static final String KEY_DESCRIPTION          = "description";
    public static final String KEY_START_TIME           = "start_time";
    public static final String KEY_ADDRESS              = "address";
    public static final String KEY_CITY                 = "city";
    public static final String KEY_STATE                = "state";
    public static final String KEY_ZIP                  = "postal_code";
    public static final String KEY_LNG                  = "lng";
    public static final String KEY_LAT                  = "lat";
    public static final String KEY_PHONE                = "phone";

    public static String COLUMNS_VENUES[] = { 
        DatabaseConstants.KEY_ROWID, 
        DatabaseConstants.KEY_EVENT_ID,
        KEY_NAME, 
        KEY_DESCRIPTION, 
        KEY_START_TIME,
        KEY_ADDRESS, 
        KEY_CITY, 
        KEY_STATE,
        KEY_ZIP, 
        KEY_LNG, 
        KEY_LAT, 
        KEY_PHONE,
        DatabaseConstants.KEY_CREATED_AT
    };

    public static final String CREATE_STATEMENT = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("
            + "_id              INTEGER PRIMARY KEY AUTOINCREMENT,"
            + "event_id         INTEGER NOT NULL,"
            + "name             TEXT NOT NULL,"
            + "description      TEXT,"
            + "start_time       TEXT,"
            + "address          TEXT NOT NULL,"
            + "city             TEXT NOT NULL,"
            + "state            TEXT NOT NULL,"
            + "postal_code      TEXT,"
            + "lat              TEXT,"
            + "lng              TEXT,"
            + "phone            TEXT,"
            + "created_at       TEXT);";

    public VenuesDataSource(Context context) {
        super(context);

        Log.i("VenueDataSource", "Constructor");
    }

    // create a new contact locally
    public long createVenue(Integer venue_id, Integer event_id, String name, String description, String start_time, String address, String city, String state, String postal_code, String lat, String lng, String phone, String created_at) {
        ContentValues initialValues = new ContentValues();

        initialValues.put(DatabaseConstants.KEY_ROWID, event_id);
        initialValues.put(DatabaseConstants.KEY_EVENT_ID, event_id);
        initialValues.put(KEY_NAME, name);
        initialValues.put(KEY_DESCRIPTION, description);
        initialValues.put(KEY_START_TIME, start_time);
        initialValues.put(KEY_ADDRESS, address);
        initialValues.put(KEY_CITY, city);
        initialValues.put(KEY_STATE, state);
        initialValues.put(KEY_ZIP, postal_code);
        initialValues.put(KEY_PHONE, phone);
        initialValues.put(KEY_LAT, lat);
        initialValues.put(KEY_LNG, lng);
        initialValues.put(DatabaseConstants.KEY_CREATED_AT, created_at);

        return getWritableDatabase().insert(TABLE_NAME, null, initialValues);
    }

    // retrieve a venue from the local database
    public Cursor getVenue(long rowId) throws SQLException {
        Cursor mCursor = getWritableDatabase().query(true, TABLE_NAME, COLUMNS_VENUES, DatabaseConstants.KEY_ROWID + "=" + rowId, null, null, null, null, null);

        if (mCursor != null) {
            mCursor.moveToFirst();
        }

        return mCursor;
    }

    // update a local venue
    public long updateVenue(Integer venue_id, Integer event_id, String name, String description, String start_time, String address, String city, String state, String postal_code, String lat, String lng, String phone) {
        ContentValues initialValues = new ContentValues();

        initialValues.put(DatabaseConstants.KEY_EVENT_ID, event_id);
        initialValues.put(KEY_NAME, name);
        initialValues.put(KEY_DESCRIPTION, description);
        initialValues.put(KEY_START_TIME, start_time);
        initialValues.put(KEY_ADDRESS, address);
        initialValues.put(KEY_CITY, city);
        initialValues.put(KEY_STATE, state);
        initialValues.put(KEY_ZIP, postal_code);
        initialValues.put(KEY_PHONE, phone);
        initialValues.put(KEY_LAT, lat);
        initialValues.put(KEY_LNG, lng);

        return getWritableDatabase().update(TABLE_NAME, initialValues, "_id=?", new String[] { Long.toString(venue_id) });
    }

    // delete a local venue
    public boolean deleteVenue(long rowId) {
        return (getWritableDatabase().delete(TABLE_NAME, DatabaseConstants.KEY_ROWID + "=" + rowId, null) > 0);
    }

    // retrieve all local venues
    public Cursor getAllVenues(long event_id) {
        Cursor mCursor = getWritableDatabase().query(true, TABLE_NAME, COLUMNS_VENUES, DatabaseConstants.KEY_EVENT_ID + "=" + event_id, 
                null, null, null, null, null);

        return mCursor; 
    }

    public boolean venueExists(int venue_id) {
        Cursor mCursor = getWritableDatabase().query(true, TABLE_NAME, COLUMNS_VENUES, DatabaseConstants.KEY_ROWID + "=" + venue_id,
                    null, null, null, null, null);

        if (mCursor.getCount() == 0) {
            mCursor.close();
            return false;
        } else {
            mCursor.close();
            return true;
        }
    }

    public void parseVenue(JSONObject venueJson) throws JSONException {
        boolean venue_exists = false;

        int event_id, venue_id;
        String venue_name, venue_description, start_time, venue_address, venue_city, venue_state, venue_postal_code, lat, lng, venue_phone, created_at;

        venue_id            = venueJson.getInt(DatabaseConstants.KEY_REMOTE_ID);
        event_id            = venueJson.getInt(DatabaseConstants.KEY_EVENT_ID);
        venue_name          = venueJson.getString(KEY_NAME);
        venue_description   = venueJson.getString(KEY_DESCRIPTION);
        start_time          = venueJson.getString(KEY_START_TIME);
        venue_address       = venueJson.getString(KEY_ADDRESS);
        venue_city          = venueJson.getString(KEY_CITY);
        venue_state         = venueJson.getString(KEY_STATE);
        venue_postal_code   = venueJson.getString(KEY_ZIP);
        venue_phone         = venueJson.getString(KEY_PHONE);
        lat                 = venueJson.getString(KEY_LAT);
        lng                 = venueJson.getString(KEY_LNG);
        created_at          = venueJson.getString(DatabaseConstants.KEY_CREATED_AT);

        // check to see if this venue already exists
        venue_exists = this.venueExists(venue_id);

        if (venue_exists == true) {
            this.updateVenue(
                    venue_id, 
                    event_id, 
                    venue_name,
                    venue_description,
                    start_time,
                    venue_address, 
                    venue_city, 
                    venue_state,
                    venue_postal_code, 
                    lat,
                    lng, 
                    venue_phone);
        } else {
            this.createVenue(
                    venue_id,
                    event_id,
                    venue_name,
                    venue_description,
                    start_time,
                    venue_address, 
                    venue_city,
                    venue_state,
                    venue_postal_code, 
                    lat,
                    lng,
                    venue_phone,
                    created_at);
        }
    }

    public void parseVenues(JSONArray venuesArray) throws JSONException {
        JSONObject venueJson;

        for (int i = 0; i < venuesArray.length(); i++) {
            // Iterate through each venue
            venueJson = venuesArray.getJSONObject(i);

            this.parseVenue(venueJson);
        }
    }
}

Answer 1:

的问题是,initLoader方法的第一个参数是一种方法,唯一地识别每个项目被装载,所有矿井被设置为相同的(0)值-它们改变为固定的问题的唯一值。



Answer 2:

的问题是,你的Fragment s的直接操纵ActivityLoaderManager ,并且作为结果的第一Loader与ID 0总是被重复使用。

在您的应用程序的每个活动和片段都有自己的LoaderManager实例,因此,而不是直接从片段操纵活动的LoaderManager,你应该有你的片段利用自身LoaderManager的。 也就是说,不是,

// initialize a new Loader that will be managed by the Activity's LoaderManager
getActivity().getSupportLoaderManager().initLoader(0, null, this);

你应该有每个人分片落实LoaderCallbacks,然后在onActivityCreated,各有一个呼叫,

// initialize a new Loader that will be managed by the Fragment's LoaderManager
getSupportLoaderManager().initLoader(0, null, this);

在碎片上的文件,他们最看重的事情之一就是碎片应设计为重复使用,不应该针对特定的活动。 因此,你的“黑客”(即提供不同的装载每个片段IDS)在技术上是“坏习惯”,因为连接到特定活动时,您的片段才能正常工作。 您希望您的片段与任何活动工作,所以通常要超过自己的用户界面,因为它可以尽可能多的控制收拾碎片。



Answer 3:

对我来说,在类,这是

    public abstract class AbstractListFragment extends SherlockFragment implements
    LoaderCallbacks<Cursor> 

与未加载内容的问题是固定通过注释的代码的一个第一线以及将所述第二个:

    // getActivity().getSupportLoaderManager().initLoader(0, null, this);
    getLoaderManager().initLoader(0, null, this);


文章来源: SherlockFragmentActivity with multiple ListFragments and SQLite cursor battles