Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can not perform this action inside of onLoadFinished

I'm creating a new sample android app with one activity and many fragments.

At first i'm starting the "OverviewFragment" (my Dashboard) which should show some basic informations. My OverviewFragment loads the informations with an "AccountProvider" (ContentProvider). If the database is empty, the OverviewFragment should be replaces with my WelcomeFragment...

Here is some code:

public class OverviewFragment extends BaseFragment
    implements LoaderManager.LoaderCallbacks {

private static final int LOADER_ACCOUNTS = 10;
private static final int LOADER_TRANSACTIONS = 20;

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    getLoaderManager().initLoader(LOADER_ACCOUNTS, null, this);
    //getLoaderManager().initLoader(LOADER_TRANSACTIONS, null, this);
}

@Override
public Loader onCreateLoader(int id, Bundle args) {
    Uri uri = null;
    String[] projection = null;
    String selection = null;
    String[] selectionArgs = null;
    String sortOrder = null;

    switch (id) {
        case LOADER_ACCOUNTS:
            uri = WalletProvider.CONTENT_URI;
            projection = new String[]{DBHelper.ACC_ID, DBHelper.ACC_TITLE};
            sortOrder = DBHelper.ACC_TITLE;
            break;
    }
    CursorLoader cl =  new CursorLoader(getActivity(),
            uri, projection, selection, selectionArgs, sortOrder);
    return cl;
}

@Override
public void onLoadFinished(Loader loader, Object data) {
    switch (loader.getId()) {
        case LOADER_ACCOUNTS:
            bindAccounts((Cursor) data);
            break;
    }

}

@Override
public void onLoaderReset(Loader loader) {

}

private void bindAccounts(Cursor cursor) {
    boolean showCreateWallet = true;


    if (cursor != null && cursor.moveToFirst()) {
        showCreateWallet = false;
    }

    if (showCreateWallet) {
        listener.changeFragment(new WalletCreateFragment());
    }
}

and here my main activity

    @Override
public void changeFragment(Fragment fragmentToLoad) {
    FragmentManager fragmentManager = getSupportFragmentManager();
    fragmentManager.beginTransaction()
            .replace(R.id.container, fragmentToLoad)
            .commit();
}

Now... if i'm starting my app with an empty database i get the error you see in the title..

I know that i should not change the fragment in the onLoadFinished function .... but where can i do it? :P

Sorry for my english =)

like image 519
Tobias Avatar asked Apr 01 '14 14:04

Tobias


2 Answers

This is a bug of android. To solve this, use a Handler to replace/add/call a Fragment inside of onLoadFinished like this:

final int WHAT = 1;
Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {                    
        if(msg.what == WHAT) changeFragment(fragmentToLoad);            
    }
};
handler.sendEmptyMessage(WHAT);
like image 67
pablobaldez Avatar answered Oct 25 '22 17:10

pablobaldez


This is not a bug, but it's not a simple issue. onLoadFinished may run at any time, including after an activity's state has been saved. The resulting fragment change will then not automatically be saved by the framework. You have to explicitly allow this using commitAllowingStateLoss

In the documentation of onLoadFinished:

Called when a previously created loader has finished its load. 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.

Quoting Dianne Hackborn from the thread referred to in the comment on the other answer:

Just understand that if the activity's fragment state has already been saved, your code will need to correctly update it if it is later restarted from the state.

[...]

It's a bad user experience to show a dialog (or do any other major shift in the UI) as the result of a loader. Here is what you are doing: setting off some operation to run in the background for an in-determinant amount of time, which upon completion may throw something in front of the user yanking them out of whatever they were doing.

My suggestion is to show the user whatever information about the loader result in-line in the same way you would show the data from it.

If you (and your users) are ok with the user experience, you can use commitAllowingStateLoss to update the fragment.

like image 39
beetstra Avatar answered Oct 25 '22 17:10

beetstra