我正在使用扩展 SherlockFragmentActivity 的Activity,它有3个标签。 3个选项卡是 ListFragments ,用于实现 LoaderManager.LoaderCallbacks 。 Activity的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()
不同的光标编辑:这是堆栈跟踪发送给我的最后一个地方 当我设置一些断点时,我可以看到这里提供的光标是来自第一个标签的光标,而不是Venue标签的光标。
编辑: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);
}
}
}
答案 0 :(得分:3)
问题是 initLoader 方法的第一个参数是唯一标识要加载的每个项目的方法,我的所有项目都设置为相同的(0)值 - 将它们更改为一个独特的价值解决了这个问题。
答案 1 :(得分:3)
问题是您的Fragment
正在直接操纵Activity
的{{1}},因此第一个LoaderManager
ID为Loader
的是总是被重用。
您应用中的每个Activity和Fragment都有自己的LoaderManager实例,因此您不应直接从Fragment操作Activity的LoaderManager,而应让Fragment使用自己的LoaderManager。也就是说,而不是
0
你应该让每个Fragment实现LoaderCallbacks,然后在onActivityCreated中,每个人调用一次,
// initialize a new Loader that will be managed by the Activity's LoaderManager
getActivity().getSupportLoaderManager().initLoader(0, null, this);
在Fragments的文档中,他们最强调的一点是Fragments应该被设计为可重用,而不应该特定于特定的Activity。因此,您的“黑客”(即为每个片段提供不同的Loader ID)在技术上是“不好的做法”,因为您的片段只有在附加到该特定活动时才能正常工作。你希望你的片段可以与任何Activity一起使用,所以你通常希望尽可能多地控制你自己的UI片段。
答案 2 :(得分:0)
对我来说是
public abstract class AbstractListFragment extends SherlockFragment implements
LoaderCallbacks<Cursor>
通过注释第一行代码并添加第二行代码来修复未加载内容的问题:
// getActivity().getSupportLoaderManager().initLoader(0, null, this);
getLoaderManager().initLoader(0, null, this);