Anna Forrest
Anna Forrest

Reputation: 1741

error on restart loader call

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

Answers (1)

James McCracken
James McCracken

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

Related Questions