Reputation: 1741
I have a fragment which loads data into a list adapter to display on screen. When my app starts it starts up a service to look for new data which the fragment will need to display if any is found. At the moment, my private broadcast receiver is actually on the main activity - rather than the fragment. That doesn't seem right to me, but I could not see how to pass the notification from the broadcast receiver back to the fragment when it was on the fragment itself and another question I read was suggesting this approach.
With the code I am posting below I get when my broadcast receiver gets a notification and call updateConent on the fragment - I get the following errors logged:
10-26 20:31:21.328: E/AndroidRuntime(711): FATAL EXCEPTION: main
10-26 20:31:21.328: E/AndroidRuntime(711): java.lang.RuntimeException: Error receiving broadcast Intent { act=updatedata flg=0x10 (has extras) } in com.thedailydigi.dailydigi.Main$1@41458418
10-26 20:31:21.328: E/AndroidRuntime(711): at android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:765)
10-26 20:31:21.328: E/AndroidRuntime(711): at android.os.Handler.handleCallback(Handler.java:615)
10-26 20:31:21.328: E/AndroidRuntime(711): at android.os.Handler.dispatchMessage(Handler.java:92)
10-26 20:31:21.328: E/AndroidRuntime(711): at android.os.Looper.loop(Looper.java:137)
10-26 20:31:21.328: E/AndroidRuntime(711): at android.app.ActivityThread.main(ActivityThread.java:4745)
10-26 20:31:21.328: E/AndroidRuntime(711): at java.lang.reflect.Method.invokeNative(Native Method)
10-26 20:31:21.328: E/AndroidRuntime(711): at java.lang.reflect.Method.invoke(Method.java:511)
10-26 20:31:21.328: E/AndroidRuntime(711): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
10-26 20:31:21.328: E/AndroidRuntime(711): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
10-26 20:31:21.328: E/AndroidRuntime(711): at dalvik.system.NativeStart.main(Native Method)
10-26 20:31:21.328: E/AndroidRuntime(711): Caused by: java.lang.IllegalStateException: There is already a listener registered
10-26 20:31:21.328: E/AndroidRuntime(711): at android.support.v4.content.Loader.registerListener(Loader.java:130)
10-26 20:31:21.328: E/AndroidRuntime(711): at android.support.v4.app.LoaderManagerImpl$LoaderInfo.start(LoaderManager.java:260)
10-26 20:31:21.328: E/AndroidRuntime(711): at android.support.v4.app.LoaderManagerImpl.installLoader(LoaderManager.java:510)
10-26 20:31:21.328: E/AndroidRuntime(711): at android.support.v4.app.LoaderManagerImpl.createAndInstallLoader(LoaderManager.java:497)
10-26 20:31:21.328: E/AndroidRuntime(711): at android.support.v4.app.LoaderManagerImpl.restartLoader(LoaderManager.java:643)
10-26 20:31:21.328: E/AndroidRuntime(711): at com.thedailydigi.dailydigi.activity.HorizontalListFragment.updateContent(HorizontalListFragment.java:130)
10-26 20:31:21.328: E/AndroidRuntime(711): at com.thedailydigi.dailydigi.Main$1.onReceive(Main.java:36)
10-26 20:31:21.328: E/AndroidRuntime(711): at android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:755)
I gather 'Caused by: java.lang.IllegalStateException: There is already a listener registered' is the crux of the problem but I don't have a listener I've registered?
Can anyone shed some light on a) why I am currently getting the above error and b) if this is the correct way to be getting the additional data loaded into the fragment?
My full code of the relevant classes is below. Thanks for any help.
Main.java
public class Main extends FragmentActivity {
private static final String LOG_TAG = "dd-Main";
private HorizontalListFragment mDailyDigiFragment;
private BroadcastReceiver myReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
//TODO go update the UI from the database
int newRecords = intent.getIntExtra(DownloadFeedService.DOWNLOADED,0);
long date = intent.getLongExtra(DownloadFeedService.BEGIN_DATE, DownloadFeedService.NULL_DATE);
Log.d(LOG_TAG, newRecords + " new records downloaded!");
if (intent.getStringExtra(DownloadFeedService.KEY).equals(getString(R.string.daily_digi_short))) {
mDailyDigiFragment.updateContent(newRecords, date);
} else {
//TODO add our other fragments here
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.v(LOG_TAG, "Main Activity started");
registerReceiver(myReceiver, new IntentFilter(DownloadFeedService.UPDATEDATA));
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
//mDailyDigiFragment = HorizontalListFragment.newInstance(getString(R.string.daily_digi_short), 10, R.drawable.banner_dailydigi, true);
mDailyDigiFragment = HorizontalListFragment.newInstance(getString(R.string.daily_digi_short), 10, R.drawable.banner_dailydigi, true);
FragmentTransaction dailyDigiTransaction = getSupportFragmentManager().beginTransaction();
dailyDigiTransaction.add(R.id.dailydigi, mDailyDigiFragment).commit();
//TODO instantiate our other fragments here
/*
Fragment digiShowFragment = HorizontalListFragment.newInstance(getString(R.string.digi_show_short), 10, R.drawable.banner_digishow, true);
FragmentTransaction digiShowTransaction = getSupportFragmentManager().beginTransaction();
digiShowTransaction.add(R.id.digishow, digiShowFragment).commit();*/
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
HorizontalListFragment.java
public class HorizontalListFragment extends Fragment
implements LoaderManager.LoaderCallbacks<ArrayList<RssFeed.Article>> {
private ArticleAdapter adapter ;
private static final String LOG_TAG = "dd-HorizontalListFragment";
private static String MAX_LOAD_KEY = "max";
private static String DATASOURCE_KEY = "key";
private static String IMAGE_SRC_KEY = "image";
private static String SHOW_TEXT_OVERLAY = "showOverlay";
private String mRssFeedKey = "";
private int mDefaultLoad;
private int mImageResource = 0;
//TODO do we want to toggle the overlay on the digishow feed?
private boolean mShowTextOverlay = true;
private HorizontalListView mListView;
ArticleLoader mLoader;
public static HorizontalListFragment newInstance(String key, int max, int img, boolean showOverlay) {
HorizontalListFragment myFragment = new HorizontalListFragment();
Bundle args = new Bundle();
args.putString(DATASOURCE_KEY, key);
args.putInt(MAX_LOAD_KEY, max);
args.putInt(IMAGE_SRC_KEY, img);
args.putBoolean(SHOW_TEXT_OVERLAY, showOverlay);
myFragment.setArguments(args);
return myFragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle bundle = this.getArguments();
mRssFeedKey = bundle.getString(DATASOURCE_KEY);
mDefaultLoad = bundle.getInt(MAX_LOAD_KEY, getResources().getInteger(R.integer.default_display_max));
mImageResource = bundle.getInt(IMAGE_SRC_KEY, 0);
mShowTextOverlay = bundle.getBoolean(SHOW_TEXT_OVERLAY, true);
mLoader = new ArticleLoader(getActivity(), mRssFeedKey,mDefaultLoad);
getActivity().getSupportLoaderManager().initLoader(0, null, this);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.horizontal_list_section, container, false);
ImageView img = (ImageView) view.findViewById(R.id.Header_Image);
img.setImageResource(mImageResource);
mListView = (HorizontalListView) view.findViewById(R.id.hr_listview);
return view;
}
@Override
public Loader<ArrayList<RssFeed.Article>> onCreateLoader(int arg0, Bundle bundle) {
Log.d(LOG_TAG, "onCreateLoader: find " + mDefaultLoad + " articles from " + mRssFeedKey);
return mLoader;
}
@Override
public void onLoadFinished(Loader<ArrayList<RssFeed.Article>> arg0, ArrayList<RssFeed.Article> result) {
Log.d(LOG_TAG, "onLoadFinished with " + result.size() + " records from " + mRssFeedKey);
//update the listview with the combined results list
if (adapter == null) {
adapter = new ArticleAdapter(this.getActivity(), result,true);
mListView.setAdapter(adapter);
} else {
insertNewArticles(result);
adapter.notifyDataSetChanged();
}
//TODO use these methods to reset the list position ?
//mListView.setSelectionFromTop(currentFirstVisibleItem, 0);
//mListView.getFirstVisiblePosition();
}
@Override
public void onLoaderReset(Loader<ArrayList<RssFeed.Article>> arg0) {
Log.v(LOG_TAG, "onLoaderReset");
// TODO what do we need to do here?
}
public void updateContent(int newRecords , long date) {
if (date == DownloadFeedService.NULL_DATE) {
Log.d(LOG_TAG, "updateContent requesting " + newRecords + " new records.");
//this is when some new recent content has been downloaded
mLoader.setDownloadParameters(newRecords, 0);
getActivity().getSupportLoaderManager().restartLoader(0, null, this);
} else {
//this was probably downloaded cuz we're scrolling near the end
}
}
//both lists should already be sorted in date desc order
private void insertNewArticles(ArrayList<RssFeed.Article> listB) {
//TODO - we need to make sure we only add new entries
int listPosition = 0;
for (Article a : listB ) {
for (int i = listPosition; i < adapter.getCount(); i++) {
Article b = adapter.getItem(i);
//already in the list - skip over it
if (a.getInternalId() == b.getInternalId()) {
listPosition = i+1;
break;
}
//our new item is published after - it shows at the front of the list
if (a.getPublicationDate().before(b.getPublicationDate())) {
adapter.insert(a, i-1);
listPosition = i;
break;
}
}
//when we get to new items that pre-date our current list items, add them at the end
if (listPosition == adapter.getCount()) {
adapter.insert(a, listPosition);
listPosition++;
}
}
}
}
ArticleLoader.java
public class ArticleLoader extends AsyncTaskLoader<ArrayList<RssFeed.Article>> {
private static final String LOG_TAG = "dd-ArticleLoader";
ArrayList<RssFeed.Article> mArticles;
final String mSource;
int mOffset = 0;
int mDownloadCount;
public ArticleLoader(Context context, String source, int max) {
super(context);
mSource = source;
mDownloadCount = max;
}
public void setDownloadParameters(int count, int offset) {
mOffset = offset;
mDownloadCount = count;
}
/**
* This is where the bulk of our work is done. This function is
* called in a background thread and should generate a new set of
* data to be published by the loader.
*/
@Override public ArrayList<RssFeed.Article> loadInBackground() {
Log.d(LOG_TAG, "loadInBackground: find " + mDownloadCount + " articles from " + mSource);
mArticles = ArticleManager.findArticles(getContext().getContentResolver(), mSource, mDownloadCount, mOffset);
return mArticles;
}
@Override
protected void onStartLoading() {
if(mArticles != null) {
deliverResult(mArticles);
} else {
forceLoad();
}
}
}
Upvotes: 3
Views: 2924
Reputation: 15766
The problem here is that when you call getActivity().getSupportLoaderManager().restartLoader(0, null, this);
in your updateContent()
method, it calls onCreateLoader(...)
again. Your onCreateLoader(..)
method then returns the same loader as the first time. This loader already has a registered listener.
To fix this problem, you just need onCreateLoader(..)
to return a new loader every time.
Change from:
@Override
public Loader<ArrayList<RssFeed.Article>> onCreateLoader(int arg0, Bundle bundle) {
Log.d(LOG_TAG, "onCreateLoader: find " + mDefaultLoad + " articles from " + mRssFeedKey);
return mLoader;
}
to:
@Override
public Loader<ArrayList<RssFeed.Article>> onCreateLoader(int arg0, Bundle bundle) {
Log.d(LOG_TAG, "onCreateLoader: find " + mDefaultLoad + " articles from " + mRssFeedKey);
return new ArticleLoader(getActivity(), mRssFeedKey,mDefaultLoad);
}
Upvotes: 4