11

I have an activity which loads a data list from the server using loader callbacks. I have to list out the data into a fragment which extends

SherlockListFragment 

i tried to commit the fragment using

Fragment newFragment   = CategoryFragment.newInstance(mStackLevel,categoryList);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.simple_fragment, newFragment).commit();

in onLoadFinished and it gives an IllegalStateException saying

java.lang.IllegalStateException: Can not perform this action inside of onLoadFinished

I have referred the example in actionbar sherlock, but those examples have loaders within the fragments and not the activity.

Can anybody help me with this o that I can fix it without calling the loader from the fragment!

Aswathy P Krishnan
  • 9,878
  • 7
  • 24
  • 45
  • possible duplicate of [Android - problems using FragmentActivity + Loader to update FragmentStatePagerAdapter](http://stackoverflow.com/questions/7746140/android-problems-using-fragmentactivity-loader-to-update-fragmentstatepagera) – rds Jun 22 '15 at 14:52

3 Answers3

14

Atlast, I have found a solution to this problem. Create a handle setting an empty message and call that handler onLoadFinished(). The code is similar to this.

@Override
public void onLoadFinished(Loader<List<Station>> arg0, List<Station> arg1) {
    // do other actions
    handler.sendEmptyMessage(2);
}

In the handler,

private Handler handler = new Handler()  { // handler for commiting fragment after data is loaded
    @Override
    public void handleMessage(Message msg) {
        if(msg.what == 2) {
            Log.d(TAG, "onload finished : handler called. setting the fragment.");
            // commit the fragment
        }
    }
}; 

The number of fragments depend on the requirement.

This method can be mainly used in case of stackFragments, where all fragments have different related functions.
Borys
  • 1,683
  • 1
  • 16
  • 31
Aswathy P Krishnan
  • 9,878
  • 7
  • 24
  • 45
  • 1
    Hmm I would advise against this. If you really need to stack fragments as you say, then you could just call commitAllowingStateLoss(). This will avoid the handler and the IllegalArgumentException as you'd be telling the platform.. "it's okay, I'm aware of the risks.. do it anyways". See [link](https://developer.android.com/reference/android/support/v4/app/FragmentTransaction.html#commitAllowingStateLoss()) – kwazi Sep 15 '12 at 00:49
  • Thanks kwazi, I had tried out that method. Thanks for your help. That was a good advice. Cheers :) – Aswathy P Krishnan Dec 05 '12 at 11:05
  • 4
    Just a note: calling commitAllowStateLoss is not an option when calling DialogFragment.show(). I had to use a handler for that particular type of fragment transaction. – javahead76 May 27 '13 at 04:29
  • 2
    @javahead76 Or you could override the `DialogFragment`'s `show()` method and replace the call to `commit()` with `commitAllowStateLoss()`. – Alex Lockwood Aug 11 '13 at 21:29
  • Handler is the way to go – Meanman Dec 14 '14 at 11:06
8

As per the Android docs on the onLoadFinished() method:

Note that normally an application is not allowed to commit fragment transactions while in this call, since it can happen after an activity's state is saved. See FragmentManager.openTransaction() for further discussion on this.

https://developer.android.com/reference/android/app/LoaderManager.LoaderCallbacks.html#onLoadFinished(android.content.Loader, D)

(Note: copy/paste that link into your browser... StackOverflow is not handling it well..)

So you simply should never load a fragment in that state. If you really don't want to put the Loader in the Fragment, then you need to initialize the fragment in your onCreate() method of the Activity, and then when onLoadFinished occurs, simply call a method on your fragment.

Some rough pseudo code follows:

public class DummyFragment {

     public void setData(Object someObject) {
           //do stuff
     }

public class DummyActivity extends LoaderCallbacks<Object> {

     public void onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);

           Fragment newFragment = DummyFragment.newInstance();
           FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
           ft.add(R.id.simple_fragment, newFragment).commit();

           getSupportLoaderManager.initLoader(0, null, this)
     }

     // put your other LoaderCallbacks here... onCreateLoader() and onLoaderReset()

     public void onLoadFinished(Loader<Object> loader, Object result) {
           Fragment f = getSupportLoaderManager.findFragmentById(R.id.simple_fragment);
           f.setData(result);
     } 

Obviously, you'd want to use the right object.. and the right loader, and probably define a useful setData() method to update your fragment. But hopefully this will point you in the right direction.

Blundell
  • 69,653
  • 29
  • 191
  • 215
kwazi
  • 594
  • 5
  • 8
  • Thanks for you answer! :) Maybe I was not clear about my question. I already have my homefragment focuused on, just wanted to implement stacking of next fragment, on loading some data. I found a method for imeplementing that. I will post it as an answer. +1 for your support and answer. I really didn't thought of using f.setData(result) which I could have used in the same activity within other fragments. :) – Aswathy P Krishnan Sep 14 '12 at 08:04
  • Ah, I wasn't aware. Anyways see my comment on your answer regarding a better solution. commitAllowingStateLoss() would be the way to go... – kwazi Sep 15 '12 at 00:50
0

As @kwazi answered this is a bad user experience to call FragmentTransition.commit() from onLoadFinished(). I have found a solution for this event by using ProgressDialog.

First created ProgressDialog.setOnDismissListener(new listener) for watching the onLoadFinished(). Further i do progressDialog.show() before getLoaderManager().restartLoader(). And eventually place progressDialog.dismiss() in onLoadFinished(). Such approach allow do not bind main UI thread and Loader's thread.

public class FrPersonsListAnswer extends Fragment 
         implements 
         LoaderCallbacks<Cursor>{
private ProgressDialog progressDialog;
                              @Override
 public View onCreateView(LayoutInflater inflater,
    ViewGroup container, Bundle savedInstanceState) {
  View view = inflater.inflate(R.layout.fragment_persons_list, container, false);
  
  //prepare progress Dialog
  progressDialog = new ProgressDialog(curActivity);
  progressDialog.setMessage("Wait...");
  progressDialog.setIndeterminate(true);
  progressDialog.setOnDismissListener(new OnDismissListener() {
   
   @Override
   public void onDismiss(DialogInterface dialog) {
    //make FragmentTransaction.commit() here;
    
    //but it's recommended to pass control to your Activity 
    //via an Interface and manage fragments there.
   }
  });
  
  lv = (ListView) view.findViewById(R.id.lv_out1);
  lv.setOnItemClickListener(new OnItemClickListener() {

   @Override
   public void onItemClick(AdapterView<?> parent, final View view,
     final int position, long id) {
    
    //START PROGRESS DIALOG HERE
    progressDialog.show();
    
    Cursor c = (Cursor) parent.getAdapter().getItem(position);

    // create Loader 
    getLoaderManager().restartLoader(1, null, curFragment);
   }
  });

  return view;
 }
    
 @Override
 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
  switch (loader.getId()) {
  case 1:
   //dismiss dialog and call progressDialog.onDismiss() listener
   progressDialog.dismiss();
   break;

  default:
   break;
  }
 }
victorkt
  • 12,298
  • 8
  • 49
  • 51
sanya5791
  • 71
  • 3