3

3つのタブがあるSherlockFragmentActivityを拡張するアクティビティを使用しています。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;
    }
}

私が抱えている問題は、say ..を選択すると、2番目のタブ(3番目のタブも同じです)がカーソルを読み込もうとしますが、list_itemsにデータを入力しようとすると、次のようなエラーが発生します。

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

いくつかのブレークポイントを設定した後、使用しようとしているカーソルが最初のタブのカーソルであることがわかりました。これはインフレで正常に読み込まれましたが、明らかに閉じられておらず、私が( think)getAllVenues()とは異なるカーソルを送信しています

編集:これは、スタックトレースが私に送信する最後の場所です。 いくつかのブレークポイントを設定すると、ここに提供されているカーソルが、[会場]タブではなく、最初のタブからのカーソルであることがわかります。

ここに画像の説明を入力してください

編集:getAllVenuesメソッドを表示するVenuesDataSourceコード

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);
        }
    }
}
4

3 に答える 3

3

問題は、 initLoaderメソッドの最初のパラメーターが、ロードされる各アイテムを一意に識別する方法であり、すべてのアイテムが同じ(0)値に設定されていることです。これらを一意の値に変更すると、問題が修正されました。

于 2012-06-28T18:00:44.943 に答える
3

問題は、 sが'sFragmentを直接操作しているため、 IDを持つ最初のものが常に再利用されていることです。ActivityLoaderManagerLoader0

アプリ内の各アクティビティとフラグメントには独自のLoaderManagerインスタンスがあるため、フラグメントからアクティビティのLoaderManagerを直接操作する代わりに、フラグメントで独自のLoaderManagerを使用する必要があります。つまり、代わりに

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

個々のフラグメントにLoaderCallbacksを実装させてから、onActivityCreatedでそれぞれ1つの呼び出しを行う必要があります。

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

フラグメントに関するドキュメントで、フラグメントが最も強調していることの1つは、フラグメントは再利用できるように設計する必要があり、特定のアクティビティに固有のものであってはならないということです。したがって、フラグメントはその特定のアクティビティにアタッチされている場合にのみ正しく機能するため、「ハック」(つまり、フラグメントごとに異なるローダーIDを提供する)は技術的に「悪い習慣」です。フラグメントを任意のアクティビティで機能させる必要があるため、通常は、フラグメントをパックして、フラグメントを可能な限り独自のUIに制御できるようにします。

于 2012-06-28T21:38:18.513 に答える
0

クラスの私にとっては

    public abstract class AbstractListFragment extends SherlockFragment implements
    LoaderCallbacks<Cursor> 

コンテンツをロードしない問題は、コードの1行目をコメント化し、2行目を追加することで修正されました。

    // getActivity().getSupportLoaderManager().initLoader(0, null, this);
    getLoaderManager().initLoader(0, null, this);
于 2013-01-09T07:44:13.010 に答える