Recursive entry to executePendingTransactions (Without fragments inside fragments) - android

Let me preface this post by saying that Ive searched about this issue a ton and found no answer that are targeted to my problem. Other questions about this involve Fragments inside Fragments, but that is not the case for me.
My setup is very simple. Activity hosts a ViewPager that uses a FragmentPagerAdapter with numerous fragments that are the same type.
This crash seems to be exclusive to Android 5.1.1.
Im using the support library for Fragments and call getSupportFragmentManager() for use with the adapter. The fragments display some views inside a RecyclerView.
Heres the full crash log for those interested:
java.lang.RuntimeException: Unable to destroy activity {workout.progression.lite/workout.progression.lite.ui.PerformanceTargetActivity}:
java.lang.IllegalStateException: Recursive entry to executePendingTransactions
at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:4852)
at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:4870)
at android.app.ActivityThread.access$1600(ActivityThread.java:197)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1735)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(Activity)
I dont perform anything in onPause except for unregistrering a Bus (Otto).
#Override
protected void onPause() {
BusProvider.getInstance().unregister(this);
super.onPause();
}

#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// use below code will like U
// if(info.constellation.trim().equals("")) {
// tv_age.setText(info.age);
// } else {
// tv_age.setText(info.age + " " + info.constellation);
// }
// should be like this, no if()...else
tv_age.setText(info.age + " " + info.constellation);
}

Related

Re-inflating view in handler.post when getView() is null

I recently replaced all the deprecated AsyncTask code in my apps with handlers and newSingleThreadExecutors. After retrieving response data from a remote server, I update the UI in the handler.post section of the code.
I've never personally been able to reproduce any problems with this, but on some devices (mostly oppo's, redmi's, vivo's, etc) under some real-world conditions, getView() returns null and my stop-gap attempt to re-inflate the view fails. The number of crashes has increased by a lot:
Exception java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object android.content.Context.getSystemService(java.lang.String)' on a null object reference
Rough outline of my code:
public class ResultFragment extends Fragment {
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.result, container, false);
}
public void onViewCreated(#NonNull View view, Bundle savedInstanceState) {
Bundle bundle = this.getArguments();
assert bundle != null;
String query_url = bundle.getString("query_url");
send_request(query_url);
}
void send_request(String... urls) {
Handler handler = new Handler(Looper.getMainLooper());
Executors.newSingleThreadExecutor().execute(() -> {
.....
handler.post(() -> {
context = getContext();
final TextView mTextView;
final WebView mWebView;
if (getView() != null) {
mTextView = getView().findViewById(R.id.count);
mWebView = getView().findViewById(R.id.result);
} else {
View view = LayoutInflater.from(context).inflate(R.layout.result, null); <-- crash
mTextView = view.findViewById(R.id.count);
mWebView = view.findViewById(R.id.result);
}
My understanding from the lifecycle documentation is that I should be able to get the view with this code. And I do understand that trying to re-inflate the code like this is a dangerous proposition (crashes might occur!). But how do I do so when getView() returns null?
As I say, I've never been able to replicate these crashes. So I'm open to trying anything that might work.
For general information, I'm targeting sdk version 33.
Your code will crash if the fragment view (or the entire Fragment instance) is destroyed as a result of user leaving your app or screen.
A quick fix for your issue is to cancel the handler runnable execution when the fragment view is destroyed.
#Override
public void onDestroyView() {
super.onDestroyView();
handler.removeCallbacksAndMessages(null);
}
For future development of your app you should look over more advance API to deal with this sort of issue. Ex: RxJava, Kotlin coroutines to name a few.
Small tip that will probably help you to reproduce the crash on any device/emulator - activate the developer option: "Don't keep activity", press the button that makes the network request and then immediately close the application. The thread will post a runnable that will execute after the fragment/view-fragment is destroyed -> NPE crash.
You wouldn't do this at all. If you don't have a view, reinflating it isn't going to do what you expect. It would, at best, create a new set of views that are in memory only and not displayed on the screen. In other words it would be pointless.
Also, that's not what your problem is. You problem is that the context is null. Your fragment isn't attached to any. In this case, what you probably want to do is update any persisted state (if any) and skip updating the UI.
Also, if you're inflating your UI normally on an excutor that then posts to a handler- stop. THat's not how it works. THe inflation should happen in the onCreateView function. You can fill in the views like that, but you would NEVER inflate them like that.

Layout fields of fragment are NULL on initialization

I have issue with passing data to fragments. It crashes 0.1% of all times on production. Let's say on 100k opening of activity it happens 100 times. It looks like not very often, but it very bothering me and I think that I am doing something wrong with fragments initialization with data. The thing is, that I create fragments only one time, and all other times I need to pass data to them I am doing it next way: myFragmentInstance.setData(Object someData); And crash happens because it tells that those view elements in fragment are not found and they are NULL, but everything should be fine if I have not recreated them. I am not rotating my phone, or have not enough of memory on it. It happens on network reconnect, because on network reconnect I am going to server for fresh data and then set that new data to my fragments. I have photos of fields of two fragments I use, maby some of you know what that data can tell about status of fragments at the moment of crash.
I am using library ButterKnife to initialize fields of fragments and activities, not initializing it with findById, maby it has some influence or no?
Here is link to simple project (only this issue on github): https://github.com/yozhik/Reviews/tree/master/app/src/main/java/com/ylet/sr/review
Description:
CollapsingActivity - activity with Collapsing AppBarLayout. Which loads one or two fragments into "fragment_content_holder" and it has TabLayout to switch between fragments in view pager.
In activity method onCreate() - I'm just simulating request to server (loadData), and when some fake data is loaded - I am showing fragments in view pager, on first call - I am creating new TabMenuAdapter extends FragmentPagerAdapter, populate it with fragments and save links to instances. On the next call - I don't create fragments from scratch and just populate them with fresh data.
MenuFragment1, MenuFragment1 - two fragments. MenuFragment1 - has method public void setupData(SomeCustomData data), to set new data, not recreating fragment on network reconnect.
NetworkStateReceiver - listens to network change and send notifications.
TabMenuAdapter - just simple class to hold fragments.
05-11 18:11:05.088 12279-12279/com.myProjectName E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.myProjectName, PID: 12279
java.lang.IllegalStateException: Fatal Exception thrown on Scheduler.
at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:111)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5268)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:902)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:697)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
at com.yozhik.myProjectName.view.fragments.MyFinalTermsFragment.setupMyInformation(MyFinalTermsFragment.java:145)
at com.yozhik.myProjectName.view.fragments.MyFinalTermsFragment.setupWithData(MyFinalTermsFragment.java:133)
at com.yozhik.myProjectName.view.activity.MyFinalActivity.onDataLoaded(MyFinalActivity.java:742)
at com.yozhik.myProjectName.presenter.MyFinalPresenter$1.onNext(MyFinalPresenter.java:55)
at com.yozhik.myProjectName.presenter.MyFinalPresenter$1.onNext(MyFinalPresenter.java:47)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:200)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:252)
at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:109)
at android.os.Handler.handleCallback(Handler.java:739) 
at android.os.Handler.dispatchMessage(Handler.java:95) 
at android.os.Looper.loop(Looper.java:135) 
at android.app.ActivityThread.main(ActivityThread.java:5268) 
at java.lang.reflect.Method.invoke(Native Method) 
at java.lang.reflect.Method.invoke(Method.java:372) 
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:902) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:697) 
05-11 18:11:07.953 2155-3877/? E/WifiStateMachine: Did not find remoteAddress {192.168.200.1} in /proc/net/arp
05-11 18:11:07.966 2155-3877/? E/WifiStateMachine: WifiStateMachine CMD_START_SCAN source -2 txSuccessRate=3800.62 rxSuccessRate=4732.06 targetRoamBSSID=any RSSI=-68
05-11 18:11:07.967 2155-3877/? E/WifiStateMachine: WifiStateMachine L2Connected CMD_START_SCAN source -2 2324, 2325 -> obsolete
05-11 18:11:08.021 2155-3896/? E/ConnectivityService: Unexpected mtu value: 0, wlan0
05-11 18:11:08.579 13514-13366/? E/WakeLock: release without a matched acquire!
Fragment which is crashing in method setupData because data_1_txt is NULL sometimes.
public class MenuFragment1 extends Fragment {
public SomeCustomData transferedDataFromActivity;
private TextView data_1_txt;
public static MenuFragment1 newInstance(SomeCustomData data) {
Log.d("TEST", "MenuFragment1.newInstance");
MenuFragment1 fragment = new MenuFragment1();
Bundle args = new Bundle();
args.putSerializable("DATA_FROM_ACTIVITY", data);
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("TEST", "MenuFragment1.onCreate");
if (getArguments() != null) {
this.transferedDataFromActivity = (SomeCustomData) getArguments().getSerializable("DATA_FROM_ACTIVITY");
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d("TEST", "MenuFragment1.onCreateView");
View v = inflater.inflate(R.layout.menu_fragment_1, container, false);
data_1_txt = (TextView) v.findViewById(R.id.data_1_txt);
setupInOnCreateView();
return v;
}
protected void setupInOnCreateView() {
Log.d("TEST", "MenuFragment1.setupInOnCreateView");
//initialization of all view elements of layout with data is happens here.
setupData(transferedDataFromActivity);
}
public void setupData(SomeCustomData data) {
Log.d("TEST", "MenuFragment1.setupData");
this.transferedDataFromActivity = data;
if (transferedDataFromActivity != null) {
data_1_txt.setText(transferedDataFromActivity.Name);
}
}
}
Fragment layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/green"
android:orientation="vertical">
<TextView
android:id="#+id/data_1_txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/yellow"
android:text="Test"
android:textSize="20sp" />
<include layout="#layout/description_layout" />
</LinearLayout>
in my experience if there is an error in fragments it is usually because of pre-loading of fragments in viewpager and TabMenu so what I did and Suggest you do to is to check if the fragment is visible to user and if they were, get data and other things so here is my code:
public class Fragment1 extends Fragment {
boolean visible = false;
public static Fragment1 newInstance() {
return new Fragment1();
}
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
if (visible && isResumed()) {
onResume();
} else if (visible) {
getMyData();
}
return rootView;
}
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
visible = isVisibleToUser;
if (isVisibleToUser) {
getMyData();
}
else {
}
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
}
this way if the view is not created yet and is not visible to user fragment won't do anything .
hope this helps.
I'm pretty sure your issues are due to keeping the references to fragments in an Array. Fragments have lifecycles and the references are not guaranteed to persist. As you said, it's hard to reproduce and track down exactly what's going wrong, but maybe you don't need to.
Some suggestions on how to fix this:
Do not store the references to fragments. Pretty much follow the example on Google's page (https://developer.android.com/training/animation/screen-slide) and instantiate a new fragment every time it's requested.
If you are worried about performance and caching is solving it, try using FragmentStatePagerAdapter - it caches pages and manages fragments' states.
If you need to access page fragments from the main fragment (or the activity), instead of storing references, use `findFragmentByTag' which will always return the currently active instance of the fragment.
I strongly believe that things aren't that complicated as others explained. If i understand it correctly, the delay caused by the network transaction is the culprit.
Consider this scenario.
You are making a network request which makes some changes in the view
at the end.
You switch the pager. (Fragment is detached, views are
destroyed)
Here comes the callback from the network request. Guess what! A crash.
So when dealing dealing with views of fragments, it's always a good idea to be more careful. I usually do this.
//in the base class
protected boolean isSafe()
{
return !(this.isRemoving() || this.getActivity() == null || this.isDetached()
|| !this.isAdded() || this.getView() == null);
}
//usage in derived classes
onNewtworkResult(Result result) {
if(!isSafe())
return;
//rest of the code
}
Alternatively, You can wrap the potential code in a try catch too. But that is more like a blind shot (At least in this case).

Restore Fragment within a Fragment Pager Adapter

I have a Fragment Pager Adapter with 5 fragments with a RecyclerView, all the same but the last one, which has a button to alter the contents, when I press this button it opens a Dialog Fragment that will replace all the contents of the RecyclerView.
My problem comes only when I send the app to background while the Dialog Fragment is open, when I resume the app and submit the changes, the app crashes showing that the RecyclerView is null.
How can I recover the full state of the Fragment after resuming.
By Logging the Life cycle I can see that after resume OnCreate, OnCreateView & OnActivityCreated are run, but my method setUpCustomPatternsRecyclerView reports that RecyclerView is null.
Below is the code for my Fragment(Very simple)
IMPORTANT: The App crashes only when I try it in my phone, in 4 different emulators, the App DO NOT crashes.
Is it the the UI is not fully drawn in my phone?
If so how can I get notified that it is fully drawn?
Fragment:
public static FragmentCustomPatterns newInstance(ArrayList<PatternArrays> patternArrays, String fragmentTitle, int patternSelectedMode) {
FragmentCustomPatterns patterns = new FragmentCustomPatterns();
Bundle args = new Bundle();
args.putParcelableArrayList(CUSTOM_PATTERNS, patternArrays);
args.putInt(PATTERN_SELECTED_MODE, patternSelectedMode);
args.putString(TITLE, fragmentTitle);
patterns.setArguments(args);
return patterns;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
patternSelectedMode = getArguments().getInt(PATTERN_SELECTED_MODE);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
openCustomCoordinatesFragment = (OpenCustomCoordinatesFragment) getActivity();
View view = inflater.inflate(R.layout.fragment_custom_patterns, container, false);
FloatingActionButton floatingActionButton = view.findViewById(R.id.fab_custom_patterns);
floatingActionButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
openCustomCoordinatesFragment.openCoordinatesDialog();
}
});
recyclerFlag = false;
return view;
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
activityPatternSelector = (ActivityPatternSelector) getActivity();
setUpCustomPatternsRecyclerView();
}
public void setUpCustomPatternsRecyclerView() {
recyclerPatternsView = getView().findViewById(R.id.custom_pattern_recycler_view);
adapter = new PatternCoordinatesAdapter(getActivity(),activityPatternSelector.getCustomPatternArrays(), patternSelectedMode);
recyclerPatternsView.setAdapter(adapter);
Utilities utilities = new Utilities(getActivity(), R.layout.item_coordinates_pattern_cardview);
GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(), utilities.calculateNoOfColumns());
recyclerPatternsView.setLayoutManager(gridLayoutManager);
recyclerPatternsView.setItemAnimator(new DefaultItemAnimator());
if (!recyclerFlag) {
recyclerPatternsView.addItemDecoration(new GridSpacing(utilities.calculateSpacing()));
recyclerFlag = true;
}
}
public interface OpenCustomCoordinatesFragment {
void openCoordinatesDialog();
}
Emulator(API22 & API26) logcat of LifeCycle recreating the error:
E/TESTING ACT: onPause:
D/TESTING FRAG: onPause:
E/TESTING ACT: onSaveInstanceState:
D/TESTING FRAG: onSaveInstanceState:
D/TESTING FRAG: onResume:
Phone Logcat(API25) logcat of LifeCycle recreating the error:
E/TESTING ACT: onPause:
D/TESTING FRAG: onPause:
E/TESTING ACT: onSaveInstanceState:
D/TESTING FRAG: onSaveInstanceState:
E/TESTING ACT: onCreate:
D/TESTING FRAG: onCreate:
E/TESTING ACT: onCreate: Array Size 25
D/TESTING FRAG: onCreateView:
D/TESTING FRAG: onActivityCreated:
D/TESTING FRAG: onActivityCreated: Array Size 25
D/TESTING FRAG: onResume:
Both are totally different I'll work my way on the phone logcat to create a Bundle in the OnSaveInstanceState. Note that on the emulator OnResume is called after OnSavedInstanceState while on the phone after OnActivityCreated and none of the other method are called.
Can anybody explain this lifecycle difference between physical devices and emulator?
UPDATE:
I can't find a fix for this. Running in the activity hosting the FragmentPagerAdapter which also launches the DialogFragment:
final FragmentCustomPatterns myTempFrag = (FragmentCustomPatterns) patternsPagerAdapter.getItem(4);
System.out.println("TESTING: Added " + myTempFrag.isAdded() + " Visible " + myTempFrag.isVisible() + " In Layout " + myTempFrag.isInLayout() + " Resumed " + myTempFrag.isResumed() + " Count " + patternsPagerAdapter.getCount());
In normal conditions returns:
TESTING: 'Added' true 'Visible' true 'In Layout' false 'Resumed' true Count 5
After sending the app to background with the Dialog Fragment open, and back to the App again, returns:
TESTING: 'Added' false 'Visible' false 'In Layout' false 'Resumed' false Count 5
I assume that this is the reason that I get:
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.support.v7.widget.RecyclerView.swapAdapter(android.support.v7.widget.RecyclerView$Adapter, boolean)' on a null object reference
Does this means that the fragment is not there?,
How do I interpret this last message?,
How do I fix the 'Added' to true?
PARTIAL FIX:
I have a "Restart Activity" Method, and I'm using it to "fix" my problem, but newbie as I am I don't think that's the proper answer.
Set the RecyclerView on onCreateView, the restoring should go automatically.
About the lifecycle: The difference exists because your emulator didn't destroy your activity / fragments and your device did, probably because the emulator has more memory.
If you would have opened many more apps on the emulator before re-opening your own app, you would have had the same. Or if you, on the emulator, put the developer option "don't keep activities" on, that mimics this behaviour: a device destroying your activities and fragments right away because it wants to use the memory elsewhere.
So any device/emulator can take any of the two routs you see. You should support both.

When is fragment finally attached to activity?

I have a main fragment with a viewpager inside it. This viewpager has 2 pages (list fragments). When I start the activty, the main fragment is shown and I also show the first paged fragment. This paged fragment displays data from a db using AsyncTask.
In the main fragment I have:
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
onPageSelected(0);
}
#Override
public void onPageSelected(int position) {
Fragment fragment = (Fragment) pagerAdapter.instantiateItem(viewPager, position);
if (fragment instanceof IPagedFragment) {
((IPagedFragment) fragment).onShown(getActivity());
}
}
And the interface is:
public interface IPagedFragment {
void onShown(FragmentActivity activity);
}
The first issue I have is that I have to pass the activity as a parameter because when onShown gets called, the activity is still null.
Furthermore, the paged fragments use progressbar logic similar to the LoginActivity sample. I also get the following exception:
IllegalStateException: Fragment PagedFragment1{4201f758} not attached to Activity
at android.support.v4.app.Fragment.getResources(Fragment.java:620)
So what is the correct stage to start retrieving data from db once the paged fragment is fully available to the UI?
Issues like yours is the reason some developers are starting to question if fragments are really that good or useful.
Also "the correct" is debatable as you can do it in a variety of places and different developers will give you different answers, But let me try to supply you some useful info.
The attach/detach callbacks:
public void onAttach(Activity activity);
public void onDetach();
between those two methods any call to getActivity() will return the non-null activity the fragments is connected to. You can override them and use a private boolean isAttached to keep track of that call.
Also useful is the:
public void onActivityCreated (Bundle savedInstanceState)
this method is called AFTER the Activity.onCreate method. That is very important if you rely on some initialisation that happened there.
Also it's important to remember that on the moment the fragment transaction happens, the Fragment.onCreate happens after the Activity.onCreate and during rotation it happens before it.
As a general rule of thumb I use the Fragment.onStart() / Fragment.onStop() for getting/listening to data. On those calls, all the UI have been created, the fragment is attached to the activity and those callbacks don't get called if there's a dialog/popup (pause/resume does)
From the documentation:
public void onActivityCreated (Bundle savedInstanceState)
[...] tells the fragment when it is fully associated with the new activity instance.
source: http://developer.android.com/reference/android/app/Fragment.html#onActivityCreated(android.os.Bundle)
To get the reference of your activity, create a local object of fragmentActivity and get your activity reference as shown below.
private FragmentActivity fragmentActivity;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
fragmentActivity=activity;
}

Prevent Fragment recovery in Android

We are using Fragments and we don't need them to be automatically recovered when the Activity is recreated.
But Android every time when Activity::onCreate(Bundle savedInstanceState) -> super.onCreate(savedInstanceState) is called, restores Fragments even if we use setRetainInstance(false) for those Fragments.
Moreover, in those Fragments Fragment.performCreateView() is called directly without going through Fragment::onAttach() and so on. Plus, some of the fields are null inside restored Fragment...
Does anybody know how to prevent Android from restoring fragments?
P.S. We know that in case of recreating Activity for config changes it could be done by adding to manifest android:configChanges="orientation|screenSize|screenLayout. But what about recreating activity in case of automatic memory cleaning?
We finished by adding to activity:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
}
It suppresses any saved data on create/recreate cycle of an Activity and avoids fragments auto re-creation.
#goRGon 's answer was very useful for me, but such use cause serious problems when there is some more information you needs to forward to your activity after recreate.
Here is improved version that only removes "fragments", but keep every other parameters.
ID that is removed from bundle is part of android.support.v4.app.FragmentActivity class as FRAGMENTS_TAG field. It may of course change over time, but it's not expected.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(createBundleNoFragmentRestore(savedInstanceState));
}
/**
* Improve bundle to prevent restoring of fragments.
* #param bundle bundle container
* #return improved bundle with removed "fragments parcelable"
*/
private static Bundle createBundleNoFragmentRestore(Bundle bundle) {
if (bundle != null) {
bundle.remove("android:support:fragments");
}
return bundle;
}
I was having a problem with TransactionTooLargeException. So thankfully after using tolargetool I founded that the fragments (android:support:fragments) were been in memory, and the transaction became too large. So finally I did this, and it worked great.
#Override
public void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable("android:support:fragments", null);
}
Edit: I added it to the Activity. In my case I have one single Activity app and Multiple Fragments.
Those who got NPE with ViewPager when use this method described in the accepted answer, please override
ViewPager.onRestoreInstanceState(Parcelable state)
method and call
super.onRestoreInstanceState(null);
instead.
I removed the fragments in Activity's onCreate.
For an app with a ViewPager, I remove the fragments in onCreate(), before their creation.
Based on this thread: Remove all fragments from container, we have:
FragmentManager fm = getSupportFragmentManager();
for (Fragment fragment: fm.getFragments()) {
fm.beginTransaction().remove(fragment).commitNow();
}
Use this one for androidx
override fun onCreate(savedInstanceState: Bundle?) {
preventFragmentRecreation()
super.onCreate(savedInstanceState)
}
private fun preventFragmentRecreation() {
supportFragmentManager.addFragmentOnAttachListener { _, _ ->
savedStateRegistry.unregisterSavedStateProvider("android:support:fragments")
}
}
This worked for me
#Override
public void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
outState.remove("androidx.lifecycle.BundlableSavedStateRegistry.key");
}
View hierarchy in not restored automatically. So, in Fragment.onCreateView() or Activity.onCreate(), you have to restore all views (from xml or programmatically). Each ViewGroup that contains a fragment, must have the same ID as when you created it the first time. Once the view hierarchy is created, Android restores all fragments and put theirs views in the right ViewGroup thanks to the ID. Let say that Android remembers the ID of the ViewGroup on which a fragment was. This happens somewhere between onCreateView() and onStart().

Categories

Resources