Je reçois des rapports d'utilisateurs de mon application sur le marché, avec l'exception suivante:
Java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at Android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.Java:1109)
at Android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.Java:399)
at Android.app.Activity.onBackPressed(Activity.Java:2066)
at Android.app.Activity.onKeyUp(Activity.Java:2044)
at Android.view.KeyEvent.dispatch(KeyEvent.Java:2529)
at Android.app.Activity.dispatchKeyEvent(Activity.Java:2274)
at com.Android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.Java:1803)
at Android.view.ViewGroup.dispatchKeyEvent(ViewGroup.Java:1112)
at Android.view.ViewGroup.dispatchKeyEvent(ViewGroup.Java:1112)
at Android.view.ViewGroup.dispatchKeyEvent(ViewGroup.Java:1112)
at com.Android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.Java:1855)
at com.Android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.Java:1277)
at Android.app.Activity.dispatchKeyEvent(Activity.Java:2269)
at com.Android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.Java:1803)
at Android.view.ViewGroup.dispatchKeyEvent(ViewGroup.Java:1112)
at Android.view.ViewGroup.dispatchKeyEvent(ViewGroup.Java:1112)
at Android.view.ViewGroup.dispatchKeyEvent(ViewGroup.Java:1112)
at Android.view.ViewGroup.dispatchKeyEvent(ViewGroup.Java:1112)
at Android.widget.TabHost.dispatchKeyEvent(TabHost.Java:297)
at Android.view.ViewGroup.dispatchKeyEvent(ViewGroup.Java:1112)
at Android.view.ViewGroup.dispatchKeyEvent(ViewGroup.Java:1112)
at Android.view.ViewGroup.dispatchKeyEvent(ViewGroup.Java:1112)
at com.Android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.Java:1855)
at com.Android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.Java:1277)
at Android.app.Activity.dispatchKeyEvent(Activity.Java:2269)
at com.Android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.Java:1803)
at Android.view.ViewRoot.deliverKeyEventPostIme(ViewRoot.Java:2880)
at Android.view.ViewRoot.handleFinishedEvent(ViewRoot.Java:2853)
at Android.view.ViewRoot.handleMessage(ViewRoot.Java:2028)
at Android.os.Handler.dispatchMessage(Handler.Java:99)
at Android.os.Looper.loop(Looper.Java:132)
at Android.app.ActivityThread.main(ActivityThread.Java:4028)
at Java.lang.reflect.Method.invokeNative(Native Method)
at Java.lang.reflect.Method.invoke(Method.Java:491)
at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:844)
at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:602)
at dalvik.system.NativeStart.main(Native Method)
Apparemment, cela a quelque chose à voir avec FragmentManager, que je n'utilise pas. Le stacktrace ne montre aucune de mes propres classes, donc je n'ai aucune idée où cette exception se produit et comment la prévenir.
Pour mémoire: j'ai un onglet, et dans chaque onglet, il y a un groupe d'activités basculant entre les activités.
S'il vous plaît vérifier ma réponse ici . En gros, je devais simplement:
@Override
protected void onSaveInstanceState(Bundle outState) {
//No call for super(). Bug on API Level > 11.
}
N'appelez pas à super()
sur la méthode saveInstanceState
. C'était déconner les choses ...
Ceci est un bug connu dans le package de support.
Si vous devez enregistrer l'instance et ajouter quelque chose à votre outState
Bundle
, vous pouvez utiliser les éléments suivants:
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE");
super.onSaveInstanceState(outState);
}
En fin de compte, la solution appropriée était (comme indiqué dans les commentaires) d'utiliser:
transaction.commitAllowingStateLoss();
lors de l'ajout ou de l'exécution de la FragmentTransaction
à l'origine de la Exception
.
Il existe de nombreux problèmes liés à un message d'erreur similaire. Vérifiez la deuxième ligne de cette trace de pile particulière. Cette exception est spécifiquement liée à l'appel à FragmentManagerImpl.popBackStackImmediate
.
Cet appel de méthode, comme popBackStack
, sera toujours échouera avec IllegalArgumentException
si l’état de la session a déjà été enregistré. Vérifiez la source. Vous ne pouvez rien faire pour empêcher cette exception d'être levée.
super.onSaveInstanceState
ne vous aidera pas. commitAllowingStateLoss
ne vous aidera pas.Voici comment j'ai observé le problème:
onSaveInstanceState
est appelé.popBackStackImmediate
est tenté.IllegalStateException
est jeté.Voici ce que j'ai fait pour le résoudre:
Comme il n'est pas possible d'éviter la variable IllegalStateException
dans le rappel, attrapez et ignorez-la.
try {
activity.getSupportFragmentManager().popBackStackImmediate(name);
} catch (IllegalStateException ignored) {
// There's no way to avoid getting this if saveInstanceState has already been called.
}
Cela suffit pour empêcher l'application de se bloquer. Mais maintenant, l'utilisateur va restaurer l'application et voir que le bouton sur lequel il pensait avoir appuyé n'a pas été appuyé (pense-t-il). Le fragment de formulaire est toujours visible!
Pour résoudre ce problème, lorsque la boîte de dialogue est créée, définissez un état indiquant que le processus a démarré.
progressDialog.show(fragmentManager, TAG);
submitPressed = true;
Et enregistrez cet état dans le paquet.
@Override
public void onSaveInstanceState(Bundle outState) {
...
outState.putBoolean(SUBMIT_PRESSED, submitPressed);
}
N'oubliez pas de le recharger dans onViewCreated
Ensuite, lors de la reprise, restaurez les fragments si la tentative de soumission a déjà été effectuée. Cela empêche l'utilisateur de revenir à ce qui semble être un formulaire non soumis.
@Override
public void onResume() {
super.onResume();
if (submitPressed) {
// no need to try-catch this, because we are not in a callback
activity.getSupportFragmentManager().popBackStackImmediate(name);
submitPressed = false;
}
}
Vérifiez si l'activité isFinishing()
avant d'afficher le fragment et faites attention à commitAllowingStateLoss()
.
Exemple:
if(!isFinishing()) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
DummyFragment dummyFragment = DummyFragment.newInstance();
ft.add(R.id.dummy_fragment_layout, dummyFragment);
ft.commitAllowingStateLoss();
}
Voici une solution différente à ce problème.
En utilisant une variable membre privée, vous pouvez définir les données renvoyées comme une intention pouvant ensuite être traitée après super.onResume ();
Ainsi:
private Intent mOnActivityResultIntent = null;
@Override
protected void onResume() {
super.onResume();
if(mOnActivityResultIntent != null){
... do things ...
mOnActivityResultIntent = null;
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data){
if(data != null){
mOnActivityResultIntent = data;
}
}
Nous sommes en octobre 2017 et Google crée la bibliothèque de support Android avec le nouveau composant Lifecycle. Il fournit une nouvelle idée pour ce problème «Impossible d'effectuer cette action après onSaveInstanceState».
En bref:
Version plus longue avec expliquer: _
pourquoi ce problème vient-il?
C'est parce que vous essayez d'utiliser FragmentManager
de votre activité (ce qui va contenir votre fragment, je suppose?) Pour valider une transaction pour votre fragment. Cela donne généralement l’impression que vous essayez de faire une transaction pour un fragment à venir, tandis que l’activité Host appelle déjà la méthode savedInstanceState
(un utilisateur peut toucher le bouton principal de sorte que l’activité appelle onStop()
, dans mon cas, c’est la raison
Habituellement, ce problème ne devrait pas se produire - nous essayons toujours de charger fragment dans une activité au tout début, comme la méthode onCreate()
est un endroit parfait pour cela. Mais parfois, cela peut arriver, surtout quand vous ne pouvez pas décider quel fragment vous allez charger pour cette activité, ou que vous essayez de charger un fragment à partir d'un bloc AsyncTask
(ou tout ce qui prendra un peu de temps). Le temps, avant que la transaction de fragment ne se produise réellement, mais après la méthode onCreate()
de l'activité, l'utilisateur peut tout faire. Si l'utilisateur appuie sur le bouton d'accueil, ce qui déclenche la méthode onSavedInstanceState()
de l'activité, il y aura un crash can not perform this action
.
Si quelqu'un veut approfondir ce sujet, je lui suggère de consulter ce blog post . Il regarde profondément dans la couche de code source et explique beaucoup de choses à ce sujet. En outre, cela donne la raison pour laquelle vous ne devriez pas utiliser la méthode commitAllowingStateLoss()
pour résoudre ce problème (croyez-moi, cela n'apporte rien de bon pour votre code).
Comment régler ceci?
Devrais-je utiliser la méthode commitAllowingStateLoss()
pour charger un fragment? Nope tu ne devrais pas;
Devrais-je remplacer la méthode onSaveInstanceState
, ignorer la méthode super
qu'il contient? Nope tu ne devrais pas;
Devrais-je utiliser l'activité isFinishing
à l'intérieur magique pour vérifier si l'activité de l'hôte est au bon moment pour une transaction de fragment? Ouais ceci ressemble la bonne façon de faire.
Jetez un coup d'œil à ce que cycle de vie composant peut faire.
Fondamentalement, Google effectue une implémentation au sein de la classe AppCompatActivity
(et de plusieurs autres classes de base à utiliser dans votre projet), ce qui permet de simplifier la déterminer l’état actuel du cycle de vie}. Revenons à notre problème: pourquoi ce problème se poserait-il? C'est parce que nous faisons quelque chose au mauvais moment. Nous essayons donc de ne pas le faire, et ce problème aura disparu.
Je code un peu pour mon propre projet, voici ce que je fais en utilisant LifeCycle
. Je code dans Kotlin.
val hostActivity: AppCompatActivity? = null // the activity to Host fragments. It's value should be properly initialized.
fun dispatchFragment(frag: Fragment) {
hostActivity?.let {
if(it.lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED)){
showFragment(frag)
}
}
}
private fun showFragment(frag: Fragment) {
hostActivity?.let {
Transaction.begin(it, R.id.frag_container)
.show(frag)
.commit()
}
Comme je le montre ci-dessus. Je vérifierai l'état du cycle de vie de l'activité de l'hôte. Avec le composant Lifecycle dans la bibliothèque de support, cela pourrait être plus spécifique. Le code lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED)
signifie, si l’état actuel est au moins onResume
, pas plus tard que cela? Ce qui garantit que ma méthode ne sera pas exécutée pendant un autre état de la vie (comme onStop
).
Est-ce tout fait?
Bien sûr que non. Le code que j'ai montré indique un nouveau moyen d'empêcher l'application de planter. Mais s'il passe à l'état onStop
, cette ligne de code ne fera rien et ne montrera donc rien sur votre écran. Lorsque les utilisateurs reviennent dans l'application, ils voient un écran vide, c'est-à-dire l'activité de l'hôte vide ne montrant aucun fragment. C'est une mauvaise expérience (ouais un peu mieux qu'un crash).
Je souhaite donc qu'il y ait quelque chose de plus agréable: l'application ne plantera pas si l'état de la vie est postérieur à onResume
, la méthode de transaction prend en compte l'état de la vie; En outre, l'activité essaiera de continuer jusqu'à ce que l'action de transaction de fragment soit terminée, une fois que l'utilisateur est revenu sur notre application.
J'ajoute quelque chose de plus à cette méthode:
class FragmentDispatcher(_Host: FragmentActivity) : LifecycleObserver {
private val hostActivity: FragmentActivity? = _Host
private val lifeCycle: Lifecycle? = _Host.lifecycle
private val profilePendingList = mutableListOf<BaseFragment>()
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun resume() {
if (profilePendingList.isNotEmpty()) {
showFragment(profilePendingList.last())
}
}
fun dispatcherFragment(frag: BaseFragment) {
if (lifeCycle?.currentState?.isAtLeast(Lifecycle.State.RESUMED) == true) {
showFragment(frag)
} else {
profilePendingList.clear()
profilePendingList.add(frag)
}
}
private fun showFragment(frag: BaseFragment) {
hostActivity?.let {
Transaction.begin(it, R.id.frag_container)
.show(frag)
.commit()
}
}
}
Je maintiens une liste dans cette classe dispatcher
, pour stocker ces fragments, aucune chance de terminer l'action de transaction. Et lorsque l'utilisateur revient de l'écran d'accueil et constate qu'il y a encore un fragment en attente de lancement, il passe à la méthode resume()
sous l'annotation @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
. Maintenant, je pense que cela devrait fonctionner comme prévu.
Solution courte et efficace:
Suivez les étapes simples
Pas
Étape 1: Remplacez l'état onSaveInstanceState
dans le fragment respectif. Et enlevez la super méthode.
@Override
public void onSaveInstanceState( Bundle outState ) {
}
Étape 2: Utilisez fragmentTransaction.commitAllowingStateLoss( );
au lieu de fragmentTransaction.commit( );
tant que les opérations de fragment.
M&EACUTE;FIEZ-VOUS, l'utilisation de transaction.commitAllowingStateLoss()
pourrait entraîner une mauvaise expérience pour l'utilisateur. Pour plus d'informations sur les raisons pour lesquelles cette exception est levée, voir cet article .
J'ai trouvé une solution sale pour ce genre de problème. Si vous voulez toujours conserver votre ActivityGroups
pour quelque raison que ce soit (j'avais des raisons de limitation de temps), vous venez de mettre en œuvre
public void onBackPressed() {}
dans votre Activity
et faites du code back
dedans. même s'il n'existe pas de méthode de ce type sur les appareils plus anciens, cette méthode est appelée par les plus récents.
N'utilisez pas commitAllowingStateLoss (), il ne devrait être utilisé que dans les cas où l'état de l'interface utilisateur peut changer de manière inattendue pour l'utilisateur.
Si la transaction a lieu dans ChildFragmentManager de parentFragment, utilisez parentFragment.isResume () outside à la place.
if (parentFragment.isResume()) {
DummyFragment dummyFragment = DummyFragment.newInstance();
transaction = childFragmentManager.BeginTransaction();
trans.Replace(Resource.Id.fragmentContainer, startFragment);
}
J'ai eu un problème similaire, le scénario était le suivant:
La méthode onCreate de la activity ressemblait à ceci:
mMainFragment = (SelectionFragment) getSupportFragmentManager()
.findFragmentByTag(MAIN_FRAGMENT_TAG);
if (mMainFragment == null) {
mMainFragment = new SelectionFragment();
mMainFragment.setListAdapter(new ArrayAdapter<String>(this,
R.layout.item_main_menu, getResources().getStringArray(
R.array.main_menu)));
mMainFragment.setOnSelectionChangedListener(this);
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
transaction.add(R.id.content, mMainFragment, MAIN_FRAGMENT_TAG);
transaction.commit();
}
L'exception a été levée car lorsque la configuration est modifiée (rotation du périphérique), l'activité est créée, le fragment principal est extrait de l'historique du gestionnaire de fragments et, en même temps, il contient déjà une référence OLD l'activité détruite
changer le mode d'implémentation pour résoudre le problème:
mMainFragment = (SelectionFragment) getSupportFragmentManager()
.findFragmentByTag(MAIN_FRAGMENT_TAG);
if (mMainFragment == null) {
mMainFragment = new SelectionFragment();
mMainFragment.setListAdapter(new ArrayAdapter<String>(this,
R.layout.item_main_menu, getResources().getStringArray(
R.array.main_menu)));
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
transaction.add(R.id.content, mMainFragment, MAIN_FRAGMENT_TAG);
transaction.commit();
}
mMainFragment.setOnSelectionChangedListener(this);
vous devez définir vos écouteurs chaque fois que l'activité est créée pour éviter que les fragments ne fassent référence à d'anciennes instances détruites de l'activité.
J'obtenais cette exception lorsque j'appuyais sur le bouton Précédent pour annuler le sélecteur d'intention sur l'activité de mon fragment de carte . J'ai résolu ce problème en remplaçant le code de onResume (où j'initialisais le fragment) sur onstart () et l'application fonctionne bien. J'espère que ça aide.
Si vous effectuez une FragmentTransaction dans onActivityResult, vous pouvez définir une valeur booléenne dans onActivityResult, puis dans onResume, vous pouvez effectuer votre FragmentTransaction sur la base de la valeur booléenne. Veuillez vous référer au code ci-dessous.
@Override
protected void onResume() {
super.onResume;
if(isSwitchFragment){
isSwitchFragment=false;
bottomNavigationView.getTabAt(POS_FEED).select();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == FilterActivity.FILTER_REQUEST_EVENT && data != null) {
isSwitchFragment=true;
}
}
Je pense que l'utilisation de transaction.commitAllowingStateLoss();
n'est pas la meilleure solution . Cette exception sera levée lorsque la configuration de l'activité sera modifiée et que fragment onSavedInstanceState()
sera appelé, puis que votre méthode de rappel async tentera de valider fragment.
Une solution simple pourrait être de vérifier si l'activité modifie la configuration ou non
par exemple. vérifier isChangingConfigurations()
c'est à dire.
if(!isChangingConfigurations()) {
//commit transaction.
}
Commander ceci lien aussi
Si vous héritez de FragmentActivity
, vous devez appeler la superclasse dans onActivityResult()
:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
...
}
Si vous ne le faites pas et essayez d'afficher une boîte de dialogue de fragment avec cette méthode, vous pouvez obtenir la variable IllegalStateException
de OP. (Pour être honnête, je ne comprends pas tout à fait pourquoi le super appel résout le problème. onActivityResult()
est appelé avant onResume()
; il ne devrait donc pas être autorisé à afficher une boîte de dialogue de fragment.)
En ce qui concerne la bonne réponse de @Anthonyeef, voici un exemple de code en Java:
private boolean shouldShowFragmentInOnResume;
private void someMethodThatShowsTheFragment() {
if (this.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
showFragment();
} else {
shouldShowFragmentInOnResume = true;
}
}
private void showFragment() {
//Your code here
}
@Override
protected void onResume() {
super.onResume();
if (shouldShowFragmentInOnResume) {
shouldShowFragmentInOnResume = false;
showFragment();
}
}
La solution la plus simple et la plus simple que j'ai trouvée dans mon cas était peut-être d'éviter de faire éclater le fragment incriminé de la pile en réponse au résultat d'activité. Donc, changer cet appel dans ma onActivityResult()
:
popMyFragmentAndMoveOn();
pour ça:
new Handler(Looper.getMainLooper()).post(new Runnable() {
public void run() {
popMyFragmentAndMoveOn();
}
}
aidé dans mon cas.
Pour contourner ce problème, nous pouvons utiliser Le composant Architecture de navigation , introduit dans Google I/O 2018 . Le composant Architecture de navigation simplifie la mise en œuvre de la navigation dans une application Android.
Dans mon cas, j'ai eu cette erreur dans une méthode de substitution appelée onActivityResult. Après avoir creusé, je me suis dit que j'avais peut-être besoin d'appeler 'super' auparavant.
Je l'ai ajouté et cela a juste fonctionné
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data); //<--- THIS IS THE SUPPER CALL
if (resultCode == Activity.RESULT_OK && requestCode == 0) {
mostrarFragment(FiltroFragment.newInstance())
}
}
Peut-être avez-vous simplement besoin d'ajouter un "super" à tout remplacement effectué avant votre code.
Chaque fois que vous essayez de charger un fragment de votre activité, assurez-vous que celle-ci est en reprise et qu'elle ne passe pas en état de pause. En état de pause, vous risquez de perdre la validation de la validation.
Vous pouvez utiliser transaction.commitAllowingStateLoss () au lieu de transaction.commit () pour charger fragment
ou
Créer un booléen et vérifier si l'activité ne se passe pas à la pause
@Override
public void onResume() {
super.onResume();
mIsResumed = true;
}
@Override
public void onPause() {
mIsResumed = false;
super.onPause();
}
puis en chargeant la vérification de fragment
if(mIsResumed){
//load the your fragment
}
Courtesy: Solution for IllegalStateException
Cette question m'avait énervé pendant beaucoup de temps, mais heureusement, je suis venue avec une solution concrète. Une explication détaillée de cela est ici .
Utiliser commitAllowStateloss () pourrait empêcher cette exception, mais entraînerait des irrégularités dans l'interface utilisateur. Jusqu'à présent, nous avons compris qu'il existait IllegalStateException lorsque nous essayons de commettre un fragment après la perte de l'état Activity. .Cela peut être simplement fait comme ça
Déclarer deux variables booléennes privées
public class MainActivity extends AppCompatActivity {
//Boolean variable to mark if the transaction is safe
private boolean isTransactionSafe;
//Boolean variable to mark if there is any transaction pending
private boolean isTransactionPending;
Maintenant, dans onPostResume () et onPause, nous définissons et désactivons notre variable booléenne isTransactionSafe. L'idée est de marquer la sécurité de la transaction uniquement lorsque l'activité est au premier plan afin d'éviter toute possibilité de perte de mémoire.
/*
onPostResume is called only when the activity's state is completely restored. In this we will
set our boolean variable to true. Indicating that transaction is safe now
*/
public void onPostResume(){
super.onPostResume();
isTransactionSafe=true;
}
/*
onPause is called just before the activity moves to background and also before onSaveInstanceState. In this
we will mark the transaction as unsafe
*/
public void onPause(){
super.onPause();
isTransactionSafe=false;
}
private void commitFragment(){
if(isTransactionSafe) {
MyFragment myFragment = new MyFragment();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.frame, myFragment);
fragmentTransaction.commit();
}
}
Ce que nous avons fait jusqu'à présent sauvera de IllegalStateException, mais nos transactions seront perdues si elles sont effectuées une fois que l'activité est passée à l'arrière-plan, un peu comme commitAllowStateloss (). Pour aider avec cela, nous avons la variable booléenne isTransactionPending
public void onPostResume(){
super.onPostResume();
isTransactionSafe=true;
/* Here after the activity is restored we check if there is any transaction pending from
the last restoration
*/
if (isTransactionPending) {
commitFragment();
}
}
private void commitFragment(){
if(isTransactionSafe) {
MyFragment myFragment = new MyFragment();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.frame, myFragment);
fragmentTransaction.commit();
isTransactionPending=false;
}else {
/*
If any transaction is not done because the activity is in background. We set the
isTransactionPending variable to true so that we can pick this up when we come back to
foreground
*/
isTransactionPending=true;
}
}
Les transactions de fragment ne doivent pas être exécutées après Activity.onStop()
! Vérifiez que vous n'avez aucun rappel qui pourrait exécuter une transaction après onStop()
. Il est préférable de résoudre le problème plutôt que d'essayer de contourner le problème avec des approches telles que .commitAllowingStateLoss()
Je sais que @Ovidiu Latcu a accepté la réponse, mais après quelque temps, l'erreur persiste.
@Override
protected void onSaveInstanceState(Bundle outState) {
//No call for super(). Bug on API Level > 11.
}
Crashlytics m'envoie toujours ce message d'erreur étrange.
Cependant, une erreur ne se produit maintenant que sur la version 7+ (Nougat) Mon correctif consistait à utiliser commitAllowingStateLoss () au lieu de commit () au niveau de fragmentTransaction.
This post est utile pour commitAllowingStateLoss () et n'a jamais eu de problème de fragment.
Pour résumer, la réponse acceptée ici pourrait fonctionner avec les versions antérieures à Android de Nougat.
Cela pourrait économiser à quelqu'un quelques heures de recherche. codages heureux. <3 acclamations
utilisez remove () au lieu de popup () si l'état est sauvegardé.
private void removeFragment() {
FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.isStateSaved()) {
List<Fragment> fragments = fragmentManager.getFragments();
if (fragments != null && !fragments.isEmpty()) {
fragmentManager.beginTransaction().remove(fragments.get(fragments.size() - 1)).commitAllowingStateLoss();
}
}
}
extension de Kotlin
fun FragmentManager?.replaceAndAddToBackStack(
@IdRes containerViewId: Int,
fragment: () -> Fragment,
tag: String
) {
// Find and synchronously remove a fragment with the same tag.
this?.findFragmentByTag(tag)?.let {
beginTransaction().remove(it).commitNow()
}
// Add a fragment.
this?.beginTransaction()?.run {
replace(containerViewId, fragment, tag)
// The next line will add the fragment to a back stack.
// Remove if not needed.
addToBackStack(null)
}?.commitAllowingStateLoss()
}
Usage:
val fragment = { SomeFragment.newInstance(data) }
fragmentManager?.replaceAndAddToBackStack(R.id.container, fragment, SomeFragment.TAG)
J'ai eu exactement le même problème… .. C'est arrivé à cause de la destruction de l'activité précédente… .. quand j'ai soutenu l'activité précédente, elle a été détruite… .. Je l'ai mise en activité de base
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SpinnerCustom2.setFragmentManager(getSupportFragmentManager());
onCreateDrawerActivity(savedInstanceState);
}
Je l'ai mis dans onStart c'était DROIT
@Override
protected void onStart() {
super.onStart();
SpinnerCustom2.setFragmentManager(getSupportFragmentManager());
}
Une autre solution de contournement possible, que je ne sais pas si aide dans tous les cas (Origine ici ):
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KitKat) {
final View rootView = findViewById(Android.R.id.content);
if (rootView != null) {
rootView.cancelPendingInputEvents();
}
}
}
Si vous rencontrez un problème avec la méthode popBackStack () ou popBackStackImmediate (), veuillez essayer fixt avec:
if (!fragmentManager.isStateSaved()) {
fragmentManager.popBackStackImmediate();
}
Cela a fonctionné pour moi aussi.
J'ai fini par créer un fragment de base et faire en sorte que tous les fragments de mon application l'étendent
public class BaseFragment extends Fragment {
private boolean mStateSaved;
@CallSuper
@Override
public void onSaveInstanceState(Bundle outState) {
mStateSaved = true;
super.onSaveInstanceState(outState);
}
/**
* Version of {@link #show(FragmentManager, String)} that no-ops when an IllegalStateException
* would otherwise occur.
*/
public void showAllowingStateLoss(FragmentManager manager, String tag) {
// API 26 added this convenient method
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (manager.isStateSaved()) {
return;
}
}
if (mStateSaved) {
return;
}
show(manager, tag);
}
}
Ensuite, lorsque j'essaie de montrer un fragment, j'utilise showAllowingStateLoss
au lieu de show
comme ça:
MyFragment.newInstance()
.showAllowingStateLoss(getFragmentManager(), MY_FRAGMENT.TAG);
Je suis venu à cette solution de ce PR: https://github.com/googlesamples/easypermissions/pull/170/files
L'exception est lancée ici (In FragmentActivity):
@Override
public void onBackPressed() {
if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
super.onBackPressed();
}
}
Dans FragmentManager.popBackStatckImmediate()
, FragmentManager.checkStateLoss()
est appelé en premier. C'est la cause de IllegalStateException
. Voir l'implémentation ci-dessous:
private void checkStateLoss() {
if (mStateSaved) { // Boom!
throw new IllegalStateException(
"Can not perform this action after onSaveInstanceState");
}
if (mNoTransactionsBecause != null) {
throw new IllegalStateException(
"Can not perform this action inside of " + mNoTransactionsBecause);
}
}
Je résous ce problème simplement en utilisant un drapeau pour marquer le statut actuel de l'activité. Voici ma solution:
public class MainActivity extends AppCompatActivity {
/**
* A flag that marks whether current Activity has saved its instance state
*/
private boolean mHasSaveInstanceState;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
mHasSaveInstanceState = true;
super.onSaveInstanceState(outState);
}
@Override
protected void onResume() {
super.onResume();
mHasSaveInstanceState = false;
}
@Override
public void onBackPressed() {
if (!mHasSaveInstanceState) {
// avoid FragmentManager.checkStateLoss()'s throwing IllegalStateException
super.onBackPressed();
}
}
}
@ Gian Gomen Dans mon cas, SUPER appelle le problème. Il semble que la solution la plus correcte soit alors commitAllowingStateLoss (), car elle résout le problème, pas le cache.
@Override
public void onRequestPermissionsResult(
final int requestCode,
@NonNull final String[] permissions,
@NonNull final int[] grantResults
) {
super.onRequestPermissionsResult(requestCode,permissions, grantResults); //<--- Without this line crash
switch (requestCode) {
case Constants.REQUEST_CODE_PERMISSION_STORAGE:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
onPermissionGranted(Constants.REQUEST_CODE_PERMISSION_STORAGE);
}
break;
}
J'ai également rencontré ce problème et ce problème se produit chaque fois que le contexte de votre FragmentActivity
est modifié (par exemple, l'orientation de l'écran est modifiée, etc.). La meilleure solution consiste donc à mettre à jour le contexte à partir de votre FragmentActivity
.
Ajoutez ceci dans votre activité
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (outState.isEmpty()) {
// Work-around for a pre-Android 4.2 bug
outState.putBoolean("bug:fix", true);
}
}
À partir de la version 24.0.0 de la bibliothèque de support, vous pouvez appeler la méthode FragmentTransaction.commitNow()
qui valide cette transaction de manière synchrone au lieu d'appeler commit()
suivi de executePendingTransactions()
. Comme documentation , cette approche est encore meilleure:
L'appel de commitNow est préférable à l'appel de commit () suivi de executePendingTransactions () car ce dernier aura pour effet secondaire de tenter de valider toutes les transactions en attente, que ce soit le comportement souhaité ou non.