The above title has been asked a lot, but answers seem closely tied to FragmentStatePagerAdapter which has nothing to do with my problem. I'm using the method putFragment(Bundle, String, Fragment) directly.
The Android Documentation for putFragment(Bundle, String, Fragment) says:
Put a reference to a fragment in a Bundle. This Bundle can be persisted as saved state, and when later restoring getFragment(Bundle, String) will return the current instance of the same fragment.
Parameters
* bundle The bundle in which to put the fragment reference.
* key The name of the entry in the bundle.
* fragment The Fragment whose reference is to be stored.
However the following code throws an exception:
Bundle bundle = new Bundle();
CustomFragment actionBarFragment = getActionBarFragment();
CustomFragment contentFragment = getContentFragment();
actionBarFragment.setArguments(bundle);
contentFragment.setArguments(bundle);
FragmentTransaction mTransaction = getSupportFragmentManager().beginTransaction();
mTransaction.add(R.id.actionBarPane, actionBarFragment);
mTransaction.add(R.id.contentPane, contentFragment);
mTransaction.commit();
getSupportFragmentManager().putFragment(bundle, "ContentFragment", contentFragment);
getSupportFragmentManager().putFragment(bundle, "ActionBar", actionBarFragment);
The reason I'm using the above is so that both ContentFragment and ActionBar fragment can use the result of getArguments() to find their opposite number, even if they aren't currently in the top of the backstack - such as if they are partially occluded by transparent Fragments higher in the stack.
However, I get the exception:
11-20 13:44:17.842: E/Default Exception Handler(12466): Caused by: java.lang.IllegalStateException: Fragment CustomFragment{4219bdb8 id=0x7f06002e com.test.CustomFragment1} is not currently in the FragmentManager
Can I conclude from this that commit() simply puts the transaction on a stack to be done on the UI thread and the putFragment() calls are happening before that transaction is being carried out? Or am I misunderstanding something? (The documentation on the Android site doesn't say anything about prerequisite states for the Fragments to be in which I assume it should).
It's worth noting the text in commit() which is why I assume the call is happening too early - A possible answer being how to attach a listener to a transaction to notify you when it has been commit()ed. I just don't think that exists...
Schedules a commit of this transaction. The commit does not happen immediately; it will be scheduled as work on the main thread to be done the next time that thread is ready.
EDIT
Confirmed that commit() is the problem by using a terrible solution:
mTransaction.commit();
new Thread() {
public void run() {
while(!contentFragment.isAdded()) {
try {
Thread.sleep(100);
} catch (Exception e) {}
}
getSupportFragmentManager().putFragment(bundle, "ContentFragment", contentFragment);
getSupportFragmentManager().putFragment(bundle, "ActionBar", actionBarFragment);
};
}.start();
Real solutions still very much appreciated.
I've never used it myself, but have you looked at FragmentManager.executePendingTransactions()? Here's what the documentation says:
After a FragmentTransaction is committed with
FragmentTransaction.commit(), it is scheduled to be executed
asynchronously on the process's main thread. If you want to
immediately executing any such pending operations, you can call this
function (only from the main thread) to do so. Note that all callbacks
and other related behavior will be done from within this call, so be
careful about where this is called from.
It sounds like it matches your use case.
Related
I lost some hours today because my code was not working any more.
The code to reload the view of a fragment was not working anymore after updating to the new version of Support Library 25.1.0:
This is my code :
FragmentManager manager = getActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = manager.beginTransaction();
fragmentTransaction.detach(fragment);
fragmentTransaction.attach(fragment);
fragmentTransaction.commit();
I have tried to debug putting some breakpoints on
public void onPause()
public void onStop()
public void onAttach(Context context)
public void onDetach()
public void onDestroyView()
public void onDestroy()
but the application is not entering into any of that function and nothing happened on the screen.
If I call detach alone, without attach, the application enter in onPause and onStop and the view leave the screen.
Faced a similar issue after updating androidx.navigation from 2.3.1 to 2.3.2.
parentFragmentManager.beginTransaction().detach(this).attach(this).commit()
has stopped reload the view of a fragment.
Everything that I found here did not help, but I noticed that separately the detach and attach operations are being performed successfully, I decided to spread their execution to different FragmentTransaction entities:
parentFragmentManager.beginTransaction().detach(this).commit ()
parentFragmentManager.beginTransaction().attach(this).commit ()
and it worked.
Hope this saves someone some time.
I've found myself facing the same issue, and found no answer online. Finally I've found out that some optimizations were added to Fragment Transactions in Revision 25.1.1 of the Support Library. (see https://developer.android.com/topic/libraries/support-library/revisions.html#25-1-1). Disabling those for your transaction will make it work as expected:
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setAllowOptimization(false);
transaction.detach(fragment).attach(fragment).commitAllowingStateLoss();
Update
setAllowOptimization is deprecated. Use setReorderingAllowed instead.
#Viad actually answered the question. To add a little bit to it, this happens in android versions 26 and above where reordering is allowed by default. Reordering comes into play when two fragment operations are requested at the same done, for example adding fragment 1 and then replacing it with fragment 2, which causes only the latter (replacing fragment 2) to happen.
So when reordering is allowed by default, when restarting the fragment using detach(fragment).attach(fragment) the first one is ignored and only second one is executed. As the fragment is already attached, attach(fragment) does not do anything. This is why none of the lifecycle methods of the fragment is called.
The resolution to the problem would be to setReorderingAllowed(false) to deactivate reordering. So the solution would be:
FragmentTransaction transaction = mActivity.getFragmentManager()
.beginTransaction();
if (Build.VERSION.SDK_INT >= 26) {
transaction.setReorderingAllowed(false);
}
transaction.detach(fragment).attach
(fragment).commit();
Here is a slight modification I made to use getSupportFragmentManager:
FragmentTransaction t = getActivity().getSupportFragmentManager().beginTransaction();
t.setAllowOptimization(false);
t.detach(this).attach(this).commitAllowingStateLoss();
To achieve the fragment's refresh with Androidx is a bit different.
As per Android Developers documentation here
As a FragmentTransaction is treated as a single atomic set of
operations, calls to both detach and attach on the same fragment
instance in the same transaction effectively cancel each other out,
thus avoiding the destruction and immediate recreation of the
fragment's UI. Use separate transactions, separated by
executePendingOperations() if using commit(), if you want to detach
and then immediately re-attach a fragment.
Therefore the code must be like this :
fun FragmentActivity.relaunchFragment(fragmentId: Int) {
val currentFragment = supportFragmentManager.findFragmentById(fragmentId)
val detachTransaction = supportFragmentManager.beginTransaction()
val attachTransaction = supportFragmentManager.beginTransaction()
currentFragment?.let {
detachTransaction.detach(it)
attachTransaction.attach(it)
detachTransaction.commit()
attachTransaction.commit()
}
}
I'm well aware of what an IllegalStateException is and why it happens when you are trying to commit FragmentTransactions after instance state has been saved. I've read through most of the popular Stackoverflow questions on the subject as well as "The Blog Post."
I have a particular scenario in which I've created a Fragment to display a custom progress spinner while the app is waiting for some search results to come in. So my search Activity is running in singleTop mode and relies on onNewIntent() to perform the search and display the spinner. This all works fine, but fails when run-time changes come in (like orientation changes). I'll get an IllegalStateException if I try removing the progress spinner Fragment after an orientation change, or when adding the spinner after a voice search. My setup looks like this:
#Override
protected void onNewIntent(Intent intent) {
if(intent.getAction().equals(Intent.ACTION_SEARCH)) {
performSearch(intent.getStringExtra(SearchManager.QUERY));
}
}
The performSearch() method sends the request to my server and then adds the progress spinner Fragment like this...
private void showProgressSpinner() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(R.anim.fragment_fade_in, R.anim.fragment_fade_out);
ProgressFragment spinner = ProgressFragment.newInstance();
transaction.add(R.id.searchContainer, spinner, "spinner");
transaction.commit();
}
Then when the search results come in through an asynchronous callback, I remove the progress spinner Fragment like this...
private void dismissProgressSpinner() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(R.anim.fragment_fade_in, R.anim.fragment_fade_out);
ProgressFragment spinner = (ProgressFragment) fragmentManager.findFragmentByTag("spinner");
if(spinner != null) {
transaction.remove(spinner);
}
transaction.commit();
}
If an orientation change comes in while the progress spinner Fragment is being displayed, I get the IllegalStateException when the search results return and I try to remove the spinner Fragment. According to Alex Lockwood, putting the FragmentTransaction in onPostResume() can help, since you are guaranteed to have the state restored by then. This indeed works, but I need to remove the Fragment in my asynchronous callback from the search results, not when the Activity resumes.
So my question is, how can I commit this Fragment transaction after a state change, but within my async callback? I've tried using commitAllowingStateLoss() but I still get the exception because my spinner Fragment is still referencing the old destroyed Activity. What's the best way to handle this?
I've tried using commitAllowingStateLoss() but I still get the exception because my spinner Fragment is still referencing the old destroyed Activity.
your fragment was recreated after config change, ie. after user have rotated screen - your spinner fragment will be destroyed and recreated, and after onAttach it will reference new activity instance. Also all of this process is done by android on UI thread in single message, so there is no chance that your async operation callback (which should execute also on UI thread) gets executed in the middle.
I assume here you are not creating some local reference to activity inside ProgressFragment.
You could write additional logic that would make sure your commit is called in valid moment. ie. in your activity onStart set some static boolean allowCommit to true, and in onPause set it to false. Also add some static variable, searchWasFinished, and in your async callback check if allowCommit is true if so then immediately remove spinner, if not then only set searchWasFinished to true. Inside your Activity.onStart check if searchWasFinished==true and if so then remove fragment with commit. This is just an idea, probably more logic would have to be put in it.
Here is my code
public void changeFragment(Fragment contentFragment, Fragment lastFragment, int resourseID,int animationType, boolean addToStack) {
if (contentFragment != lastFragment) {
mContentFragmentTransaction = getFragmentManager().beginTransaction();
FragmentTransactionExtended fragmentTransactionExtended = new FragmentTransactionExtended(this, mContentFragmentTransaction,lastFragment,contentFragment, resourseID);
fragmentTransactionExtended.addTransition(animationType);
mContentFragmentTransaction.replace(resourseID, contentFragment);
if (addToStack) {
mContentFragmentTransaction.addToBackStack(null);
}
mContentFragmentTransaction.commit();
}
}
The problem is, everytime I changeFragment using this method, the fragment's oncreate will fire multiple times, first time, it runs once, second time twice , third time three times, hope someone know what's happening here.
addToBackstack creates a snapshot of your fragments state. Which means when you press the back button, you are actually reverting to the last state that addToBackstack was called on.
You have to call replace on the container that has the fragment, not the fragment itself. So instead of calling replace(R.id.dashboard, fragment) it should be replace(R.id.dashboards_container, fragment).
have you tried to use an other method, like remove(), then do an add(). or anything similar? some people say's replace() method does not always behave correctly.
If the state of the activity has already been saved its no longer safe to call commit. You must call commitAllowingStateLoss() instead.
Even if you use replace or remove calls you can't do this. Only work around I have found is to create a new instance of a fragment and add it every time. Once you remove or replace a fragment it is best to drop all of your references to it so the GC can take care of it.
regards maven
I am current working on project which is in used Fragment. But Here, When i call to activity class From Fragment it's run perfectly. What i have to do is that on Back Pressed i need to call a Fragment.But i can't , it shows me error and my app stops.
So my question here is that how can i call fragment from activity so that my sequence should be fragment>activity>fragment.
07-11 16:22:12.190: E/AndroidRuntime(11963): java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
& when i want to call activity from fragment it's give error
07-11 15:52:25.961: E/FragmentManager(11885): No view found for id 0x7f05003c for fragment
So, how can i call fragment from activity & activity from fragment?
Try changing
transaction.commit();
to
transaction.commitAllowingStateLoss();
Or comment out the super onSaveInstance method on your activity:
#Override
protected void onSaveInstanceState(Bundle outState) {
//super.onSaveInstanceState(outState);
}
To call activity from a fragment you can use:
((YourActivity)getActivity).someMethod();
java.lang.IllegalStateException:
Can not perform this action after onSaveInstanceState
Solution:
Use transaction.commitAllowingStateLoss(); when adding or performing
the FragmentTransaction that was causing the Exception.
Why was the exception thrown?
The exception was thrown because you attempted to commit a FragmentTransaction after the activity's state had been saved, resulting in a phenomenon known as Activity state loss.
When you call FragmentTransaction#commit() after onSaveInstanceState() is called, the transaction won't be remembered because it was never recorded as part of the Activity's state in the first place. From the user's point of view, the transaction will appear to be lost, resulting in accidental UI state loss. In order to protect the user experience, Android avoids state loss at all costs, and simply throws an IllegalStateException whenever it occurs.
NOTE:
Use commitAllowingStateLoss() only as a last resort. The only difference between calling commit() and commitAllowingStateLoss() is that the latter will not throw an exception if state loss occurs. Usually you don't want to use this method because it implies that there is a possibility that state loss could happen. The better solution, of course, is to write your application so that commit() is guaranteed to be called before the activity's state has been saved, as this will result in a better user experience. Unless the possibility of state loss can't be avoided, commitAllowingStateLoss() should not be used.
More from: fragment-transaction-commit-state-loss.
FragmentManager(11885): No view found for id 0x7f05003c for fragment
Solution:
The Fragment manager cannot able to find a view with R.id.Container according to what you set in a layout at setContentView of activity.
So whatever layout you have set in setContentView, that layout doesn't contain that view with that id that resolves to id 0x7f05003c say of R.id.Container.
I've developed an app, but a user is stating the app is crashing on them in a specific part, the code I suspect to be the culprit can be found below, but to provide you with some context. I have a fragment manager that acts as a wizard, and on the last fragment is a submit button that, when clicked, "processes" the data from all pager fragments and then redirects to a confirmation fragment, all of this happening after the submit button is clicked is done within an Async Task. Also, if it is of any consequence, I am running a progress dialog while the AsyncTask is running.
//Called from Async Task's "onPostExecute()"
private void callback()
{
FragmentManager fragmentManager = getActivity()
.getSupportFragmentManager();
fragmentManager.popBackStack("fraud_report", FragmentManager.POP_BACK_STACK_INCLUSIVE);
Fragment f = new CompletedFragment();
Bundle args = new Bundle();
args.putString("id", reportToSubmit.getReferenceId());
f.setArguments(args);
fragmentManager.beginTransaction()
.replace(R.id.content_frame, f, "completed").commit();
fragmentManager.executePendingTransactions();
}
I have a guess as to what this could be, especially if it is hard to reproduce. You issue is that you are doing a FragmentTransaction after an asynchronous callback, during which any number of things can happen. Therefore you cannot guarantee where you will be in the Activity Lifecycle. The problem is Fragments and State Loss.
http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html
https://stackoverflow.com/a/17527246/1856960
Say for example, the user starts the AsyncTask, then ends the Activity, starts another one, or hits the home button. All of this will put the Activity in the background and may trigger a call to onSaveInstanceState. You cannot commit a Fragment Transaction after onSaveInstanceState is called. This is because the FragmentManager and all the state associated with it has already been bundled in onSaveInstanceState, so if the Activity is destroyed and re-created, you will lose your Fragment Transaction.
As for fixing the issue, I wish I had a good suggestion for you. You can look into FragmentTransaction#commitAllowingStateLoss but this should only be used if you are sure the potential downfalls will not negatively impact your application. See the links above for more information.