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().
Related
I've been using for a while the best practice recommended by Google of having a MyFragment.newInstance() static function. Though thinking about it, why can't we simplify it removing this static function, the call to onCreate to access the arguments, and only using one bundle to always save and retrieve the latest data when recreating the fragment ?
I made a simple test that seems to work just as fine as the slightly heavier current practice.
The state persisted after activity recreation, orientation change, and fragment re-creation in a FragmentStatePagerAdapter.
Am I missing anything?
public class TestFragment extends Fragment {
private String fragmentText;
public TestFragment() { } // Required empty public constructor
#SuppressLint("ValidFragment")
public TestFragment(String fragmentText) {
// add here other init arguments
// don't save them in any bundle yet
this.fragmentText = fragmentText;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (savedInstanceState != null) {
// retrieve all arguments here
fragmentText = savedInstanceState.getString("fragmentText", fragmentText);
}
TextView textView = new TextView(getActivity());
textView.setText(fragmentText);
return textView;
}
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
// save everything here once, only when needed
outState.putString("fragmentText", fragmentText);
}
// Add your setters to interact with the fragment
// those changes will persists after fragment re-creation
public void setFragmentText(String fragmentText) {
this.fragmentText = fragmentText;
}
}
Why isn't savedInstanceState bundle enough?
It is enough. The arguments Bundle is added to the saved instance state Bundle automatically.
I made a simple test that seems to work just as fine as the slightly heavier current practice.
Your approach is roughly the same, in terms of lines of code, as is the factory-method approach.
Why do we need to add an additional bundle with setArguments?
You do not "need" it. It is merely an available and recommended pattern for providing input to the fragment. You are welcome to do something else if you wish. Just remember to have the public zero-argument constructor as well as your custom constructor, since the framework will use the public zero-argument constructor when recreating your fragments.
Because savedInstance doesn't call every time. It will be triggered when device screen will be rotated or when inner system kill application due to low memory and some more scenarios. So if you want to pass some values from activities to fragment or fragment to fragment you must have to pass it through Argument. There are plenty of others ways -> you can make static variable and store the value in it but thats not a perfect value to pass value -> it will consume lot of memory. So passing through argument is standard way
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;
}
I have a fragment attached to the activity using XML (and setContentView() in activity). A have a problem because I have very dynamic views in my fragment, so when orientation changes
I must restore all states of views.
I have problem because I'm using something like that:
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("restore", true);
outState.putInt("nAndroids", 2);
}
But after orientation change when methods with param Bundle savedInstanceState are called (like onCreateView etc) my savedInstanceState is always null.
I'm not a noob in the Android but now I'm very angry because of this problem...
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (savedInstanceState == null) {
//smth
} else {
// smthelse THIS IS NEVER REACHED BECAUSE BUNDLE IS ALWAYS NULL
}
getListView().setDivider(getResources().getDrawable(R.drawable.list_divider));
}
All the problem was in that I don't declare android:id for the fragment in XML. Android needs ID or TAG to recognize stored fragment and reproduce all elements in it. So guys, remember - every instance of fragment needs unique id or tag!
Also, when setRetainInstance(true) is declared then bundle should always return null.
I had a similar problem where I was always getting savedInstanceState as null inspite of supplying the bundle to the Fragment.
The only solution that worked for me was to do
myFragment.setArguments(bundle)
with my bundle from the Activity and do a
Bundle bundle = this.getArguments();
in onCreateView of the fragment.
Hope this helps someone else.
For Fragment :-
use this for save state of fragment on orientation.
onCreate(Bundle save)
{
super.onCreate(save);
setRetainInstance(true);
}
See this tutorial :-
http://techbandhu.wordpress.com/2013/07/02/android-headless-fragment/
For Activity:-
When you start your application, in onCreate, your bundle object is null, so you have to put a check like below and when you rotate your screen then onSaveInstance is called and your bundle object is initialized
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game);
if (savedInstanceState != null) {
boolean t = outState.getBoolean("restore");
int s = outState.getInt("nAndroids");
}
}
First you should put your data, then call super.onSaveInstanceState(outState);
public void onSaveInstanceState(Bundle outState) {
outState.putBoolean("restore", true);
outState.putInt("nAndroids", 2);
super.onSaveInstanceState(outState);
}
And be sure that activity has not nohistory property in AndroidManifest.xml or set it to false.
<activity
android:noHistory="false">
If you are trying to use outState to save the state and destroy the fragment by navigating to another fragment, it will not work, you have in this case to save your state permanently in either sharedPreferences or if it's big and you want to be more organized you can use any persistence lib like Room, Realm, ...etc.
When should use outState and savedInstanceState only to make Fargment/Activity survive config change(rotation for example) or processes being killed by the OS when the app is in background for example.
Ok I know this is an old post but I couldn't find the right answer for me here nor many other places, so I am posting how I fixed my case.
So My Fragment is inside an Activity. And I originally tried to save Bundle only in Fragment and retrieve it at onCreateView. However that was the problem.
I fixed this by initiating myFragment object in activity and put that object to activity Bundle at onSaveInstanceState(). Then retrieved it at onRestoreInstanceState(). I used getSupportFragmentManager().putFragment/getFragment. Then the savedInstanceState in fragment was no longer null.
I've got an activity, containing fragment 'list', which upon clicking on one of its items will replace itself to a 'content' fragment. When the user uses the back button, he's brought to the 'list' fragment again.
The problem is that the fragment is in its default state, no matter what I try to persist data.
Facts:
both fragments are created through public static TheFragment newInstance(Bundle args), setArguments(args) and Bundle args = getArguments()
both fragments are on the same level, which is directly inside a FrameLayout from the parent activity (that is, not nested fragments)
I do not want to call setRetainInstance, because my activity is a master/detail flow, which has a 2 pane layout on larger screens. 7" tablets have 1 pane in portrait and 2 panes in landscape. If I retain the 'list' fragment instance, it will (I think) fuck things up with screen rotations
when the users clicks an item in the 'list' fragment, the 'content' fragment is displayed through FragmentTransaction#replace(int, Fragment, String), with the same ID but a different tag
I did override onSaveInstanceState(Bundle), but this is not always called by the framework, as per the doc: "There are many situations where a fragment may be mostly torn down (such as when placed on the back stack with no UI showing), but its state will not be saved until its owning activity actually needs to save its state."
I'm using the support library
From the bullet 5 above, I guess that low-end devices that need to recover memory after a fragment transaction may call Fragment#onSaveInstanceState(Bundle). However, on my testing devices (Galaxy Nexus and Nexus 7), the framework doesn't call that method. So that's not a valid option.
So, how can I retain some fragment data? the bundle passed to Fragment#onCreate, Fragment#onActivityCreated, etc. is always null.
Hence, I can't make a difference from a brand new fragment launch to a back stack restore.
Note: possible related/duplicate question
This doesn't seem right, but here's how I ended up doing:
public class MyActivity extends FragmentActivity {
private Bundle mMainFragmentArgs;
public void saveMainFragmentState(Bundle args) {
mMainFragmentArgs = args;
}
public Bundle getSavedMainFragmentState() {
return mMainFragmentArgs;
}
// ...
}
And in the main fragment:
public class MainFragment extends Fragment {
#Override
public void onActivityCreated(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = ((MyActivity) getActivity()).getSavedMainFragmentState();
if (args != null) {
// Restore from backstack
} else if (savedInstanceState != null) {
// Restore from saved instance state
} else {
// Create from fragment arguments
args = getArguments();
}
// ...
}
// ...
#Override
public void onDestroyView() {
super.onDestroyView();
Bundle args = new Bundle();
saveInstance(args);
((MyActivity) getActivity()).saveMainFragmentState(args);
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
saveInstance(outState);
}
private void saveInstance(Bundle data) {
// put data into bundle
}
}
It works!
if back from backstack, the fragment uses the parameters saved in onDestroyView
if back from another app/process/out of memory, the fragment is restored from the onSaveInstanceState
if created for the first time, the fragment uses the parameters set in setArguments
All events are covered, and the freshest information is always kept.
It's actually more complicated, it's interface-based, the listener is un/registered from onAttach/onDetach. But the principles are the same.
I have an activity with action bar tab. Each tab contain a fragment. Now when I rotate my device, bundle in my corresponding fragment is coming as null. This is taken care when I using device post android 3.2, but it is happening when device is Andoird3.0. I am having a headache after working on this issue. I crossed check various link on SO, but no help. Although I have given enough details, still will provide some code snippet as at various cases user ask for code snippet.
In my fragment class I am storing this value
#Override
public void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
outState.putBoolean("textboxVisible", true);
}
this is storing one boolean variable which it retrived as below.
/**
* Function called after activity is created. Use this
* method to restore the previous state of the fragment
*/
#Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null)
{
//restore the state of the text box
boolean textboxVisible = savedInstanceState.getBoolean("textboxVisible");
if (textboxVisible)
{
//do some stuff
}
}
}
but after rotation savedInstanceState is coming as null.
I don't what is going wrong. I have read in some document that below 3.2 the onCreateView() of
fragment is not called with bundle value. But to deal with this. Any help will be appreciated.
if you use setRetainInstance(true) the savedInstance bundle is always gonna be null after orientation changed. SO you cannot really save something with it, but what you can do if you need to save something, is to put it in a data member of the fragment, because setRetainInstance(true) preserves the fragment and doesn't destroy it, so after the device was rotated you gonna have the same values.
Try to get the savedInstanceState in onCreate of the Fragment.
Like
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
if (savedInstanceState != null) {
// IT MUST NOT BE NULL HERE
}
}
Please try... i hope it will work