2

In my application I have an Activity that holds 3 Fragments. The very first time the Activity is created, Fragment 1 is displayed. Next, all fragment transactions will be executed after a network operation. For example: Fragment 1 has a button to make a request to the server and when the result is ready, Fragment 1 uses a listener to call a method defined inside the parent activity, to replace fragment 1 with fragment 2. This works fine, except when the parent activity receives the callback after its state has been saved by onSaveInstanceState(). An IllegalStateException is thrown.

I've read some answers about this problem, for example this post and I understood why this exception happens thanks to this blog.

I also take an example that I found here to try to solve the problem. This post suggests to always check if the activity is running before call commit(). So I declared a Boolean variable in the parent activity and I put its value to false in onPause() and to true in onResume().

The parent activity callback called after network operations has been completed is something like this piece of Kotlin code, where next is the number of the replacing fragment:

private fun changeFragment(next:Int){
 // get the instance of the next fragment
  val currentFragment = createFragment(next)
 // do other stuff here

 if(isRunning){
   // prepare a replace fragment transaction and then commit

   ft.commit()

 }else{

   // store this transaction to be executed when the activity state become running

}

}

This code is working fine and now I'm not getting the Exception anymore, but my question is: it's possible that onSaveInstanceState() is called after I check if(isRunning) and before I call ft.commit(), so that the commit() happens after the activity state has been saved causing IllegalStateException again?

I'm not sure if onSaveInstanceState() could interrupt my changeFragment() method at any point in time. Is it possible?

If the possibility exists and my code may be interrupted between if(isRunning) and ft.commit(), what I can do? It could be solved adding a try{}catch(){} block like this?:

if(isRunning){
  try{
    ft.commit()
  }catch(ie:IllegalStateException){
    // store the transaction and execute it when the activity become running
  }
}else{
 // store the transaction and execute it when the activity become running
}
user9257465
  • 91
  • 1
  • 1
  • 6

3 Answers3

2

Its a bit late but as of API 26+ we can use following to check if we need to do a normal commit or commitAllowingStateLoss().

getSupportFragmentManager().isStateSaved();
Max
  • 510
  • 5
  • 16
0

Are you storing anything when you're changing states? If not, then you can try commitAllowingStateLoss().

Ganesh
  • 368
  • 5
  • 15
  • Yes I'm storing the activity state and the fragment instance to be restored in `onRestoreInstanceState()`. I need to do that so I don't want to use `commitAllowingStateLoss()`. Simply I want to keep my code as it is and as you can see above in my question, but I want to know if there's the possibility I explained above. – user9257465 May 08 '18 at 07:51
  • I found `executePendingTransactions();` for FragmentManager, But not sure how to implement it, You can read about it & see if it's relevent. Otherwise as @npace said, Use a reactive Pattern, which I ended up using `commitAllowingStateLoss()` – Ganesh May 08 '18 at 08:01
  • I read your answer and I look for more details about `executePendingTransactions()`. I also found this answer [https://stackoverflow.com/questions/22910536/is-executependingtransactions-always-necessary] and I'm not understanding if using `executePendingTransactions()` instead of `commit()` will solve my problem, because when the answer says "all callbacks and other related behavior will be done from within this call", I don't understand if there's still the possibility that `onSaveInstanceState()` could happen before the pending transaction is being committed. Do you know more? – user9257465 May 08 '18 at 08:29
  • Nope, I guess I've to look into that, but as I said, I didn't find a solution & I was already using MVVM, I didn't store any state, So, I ended up using commitAllowingStateLoss(). Sorry, but that's above my current knowledge. – Ganesh May 08 '18 at 08:34
0

onSaveInstanceState() would not be able to interrupt your method if your method is being called on the main (UI) thread.

Another approach that tends to make your life easier is to not use callbacks, but rather adopt a reactive pattern like MVVM. In that pattern, your Activity or Fragment subscribe to an observable when they are interested in e.g. network responses and unsubscribe typically in the onStop or onPause lifecycle callbacks so that your methods never get called after onSaveInstanceState. For a good starting place, check the official LiveData overview.

npace
  • 4,218
  • 1
  • 25
  • 35
  • To be clear, my method is declared inside the parent `activity` and is called when a `coroutine Job` (started by clicking a button inside fragment 1) ends and returns to the UI thread. So if `onSaveInstanceState()` would not be able to interrupt my method I do keep my code as it is. But I'll also take a look to the MVVM pattern you've suggested. Thank you. – user9257465 May 08 '18 at 08:10