4

Im trying to write simple music player and faced with such problem. In my UI i have several fragments, used for different modes of displaying songs (for example, all music, music from selected folder, music from playlist and so on). Also i have a service which plays music.

When user close the player in playing state i wanna to save UI state (i.e. current displaying fragment) and to restore this state after opening player again (if user close player in paused state it doesn't need).

For saving state i use my Service, mentioned above. In onDestroy() method of my activity i put all needed data for restoring to intent and call startService(intent). Service save this data to local variables. In onCreate() method of my activity i call startService with intent, contains request of saved state. In service i put saved data to intent and send it to Activity using LocalBroadcastManager.getInstance(this).sendBroadcast(intent) method. In inner BroadcastListener class of my Activity i receive this intent and extract data. It seems its okay.

But problems begins when i trying to restore Fragments which was displaying when user close the player. Application crashes with unhandled exception. Here is backstack:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
        at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1377)
        at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1395)
        at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:637)
        at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:616)
        at com.borune.test.MainActivity.switchFragments(MainActivity.java:58)
        at com.borune.test.MainActivity$MyServiceListener.onReceive(MainActivity.java:102)
        at android.support.v4.content.LocalBroadcastManager.executePendingBroadcasts(LocalBroadcastManager.java:297)
        at android.support.v4.content.LocalBroadcastManager.access$000(LocalBroadcastManager.java:46)
        at android.support.v4.content.LocalBroadcastManager$1.handleMessage(LocalBroadcastManager.java:116)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:136)
        at android.app.ActivityThread.main(ActivityThread.java:5032)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:515)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
        at dalvik.system.NativeStart.main(Native Method)

I've googling this exception but all proposed solutions didn't help in my case. I wrote a simple project repeating described problem. Here is the code:

MainActivity.java:

public class MainActivity extends ActionBarActivity {

private int current_fragment;
private FragmentA fragmentA = null;
private FragmentB fragmentB = null;
private MyServiceListener myServiceListener;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    current_fragment = 1;
    switchFragments(null);
    myServiceListener = new MyServiceListener();

    LocalBroadcastManager.getInstance(this).registerReceiver(myServiceListener,new IntentFilter(MyService.RESPONSE_RESTORE));

    requestState();
}

public void switchFragments(View view) {
    switch(current_fragment) {
        case 1 : {
            if(fragmentA == null) {
                fragmentA = FragmentA.newInstance();
            }

            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
            transaction.replace(R.id.container, fragmentA);
            current_fragment = 1;
            transaction.commit();

            current_fragment = 2;

            break;
        }
        case 2 : {
            if(fragmentB == null) {
                fragmentB = FragmentB.newInstance();
            }

            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
            transaction.replace(R.id.container, fragmentB);
            current_fragment = 1;
            transaction.commit();

            current_fragment = 1;

            break;
        }

    }
}

protected void onDestroy(){
    super.onDestroy();

    Log.d("log", "onDestroy() called, saving fragment number");
    Intent intent = new Intent(this,MyService.class);
    intent.setAction(MyService.SAVE_STATE);
    intent.putExtra(MyService.FRAGMENT_NUMBER,current_fragment);
    startService(intent);
}


private class MyServiceListener extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();

        if(action.equals(MyService.RESPONSE_RESTORE)) {
            current_fragment = intent.getIntExtra(MyService.FRAGMENT_NUMBER,-1);
            Log.d("log", "received fragment number, restoring now");
            switchFragments(null);
        }
    }
}

private void requestState() {
    Intent intent = new Intent(this,MyService.class);
    intent.setAction(MyService.REQUEST_RESTORE);
    startService(intent);
 }
 }

MyService.java

public class MyService extends Service {

public static final String REQUEST_RESTORE = "action.REQUEST_RESTORE";
public static final String RESPONSE_RESTORE = "action.RESPONSE_RESTORE";
public static final String SAVE_STATE = "action.SAVE_STATE";
public static final String FRAGMENT_NUMBER = "FRAGMENT_NUMBER";

private int fragment_number = -1;

public MyService() {
}

@Override
public IBinder onBind(Intent intent) {
    return null;
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {

    String action = intent.getAction();
    if(action.equals(REQUEST_RESTORE)) {
        if(fragment_number != -1)
            sendState();
    } else if (action.equals(SAVE_STATE)) {
        fragment_number = intent.getIntExtra(FRAGMENT_NUMBER,-1);
    }
    return START_NOT_STICKY;
}

private void sendState() {
    Intent intent = new Intent(RESPONSE_RESTORE);
    intent.putExtra(FRAGMENT_NUMBER,fragment_number);
    LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
 }
 }

In this app i have a Layout with container and Button with android:onClick="switchFragments". After closing the app and opening it again i receive the same exception.

Can anybody explain me, why this error occures and how to solve it?

Neha Shukla
  • 3,572
  • 5
  • 38
  • 69
borune
  • 548
  • 5
  • 21
  • Where do you call `LocalBroadcastManager.getInstance(this).unregisterReceiver()`? You need to unregister if you don't want to receive broadcasts after your activity is destroyed. – ianhanniballake Jul 16 '15 at 21:52

1 Answers1

5

This thread will explain what happens : getting exception "IllegalStateException: Can not perform this action after onSaveInstanceState"

Short answer but might be dangerous : use commitAllowingStateLoss() instead of commit(), but it might break something if you are not careful.

as for what I did, I created a boolean flag called "isSafeToCommitFragment", set it false by default, then assign the value to the Activity lifecycle as follows :

  • onDestroy() : false
  • onPause() : false
  • onResume() : false
  • onStart() : false
  • onStop() : false
  • onPostResume() : true

before calling fragmentTransaction.commit(), check the flag as follows :

if (isSafeToCommitFragment) {
    fragmentTransaction.commit();
}
Community
  • 1
  • 1
reidzeibel
  • 1,622
  • 1
  • 19
  • 24