Been searching for this issue for a while to no avail now:
How to determine fragment is being restored from backstack?
I'm using the compatibility library and a ListFragment inside a FragmentActivity. When an item inside ListFragment is selected, a new Fragment is started to replace the ListFragment.
I noticed that when the FragmentActivity gets paused, the Fragment's onSaveInstanceState is called. But when the Fragment is put into the back stack via FragmentTransaction, onSaveInstanceState doesn't get called, then the lifecycle methods onCreateView and onActivityCreated gets called with null savedInstanceState Bundle.
I'm asking this because I want to load some data when the Fragment is created or restored, but not so when user comes back via. backstack.
I've looked at How to check if Fragment was restored from a backstack?
but want to add more details in hopes this would incite an answer.
Edit:
just noticed http://developer.android.com/reference/android/app/Fragment.html#onSaveInstanceState(android.os.Bundle)
says
Note however: this method may be called at any time before onDestroy(). 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.
So onSaveInstanceState is definitely out of the question...
I think that most simple way is do this for example in onViewCreated() method:
if (savedInstanceState == null && !mAlreadyLoaded) {
mAlreadyLoaded = true;
// Do this code only first time, not after rotation or reuse fragment from backstack
}
Because when android put fragment on backstack, it only destroy its view, but don't kill instance itself, so mAlreadyLoaded will be still true when fragment will be restored from backstack.
getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {
public void onBackStackChanged() {
Log.i(TAG, "back stack changed ");
int backCount = getSupportFragmentManager().getBackStackEntryCount();
if (backCount == 0){
// block where back has been pressed. since backstack is zero.
}
}
});
use this addOnBackStackChangedListener.
When a fragment goes to back-stack onDestroyView() called. Not onDestroy().
And when a fragment pops from back-stack onCreateView() called. Not onCreate().
So add a boolean mIsRestoredFromBackstack to fragment and follow as below:
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
mIsRestoredFromBackstack = false;
}
#Override
public void onResume()
{
super.onResume();
if(mIsRestoredFromBackstack)
{
// The fragment restored from backstack, do some work here!
}
}
#Override
public void onDestroyView()
{
super.onDestroyView();
mIsRestoredFromBackstack = true;
}
MAJOR EDIT: Oct 15 2013
The previous explanation (kept below for reference) fails when the application is put to the background and brought back to the foreground.
Instead, it is better to compare the current size of the backstack with the one when the fragment was created & put into the backstack.
Take a good look at Figure 2 in http://developer.android.com/guide/components/fragments.html#Creating
What this figure tells you is that when a fragment is restored from the backstack, its onCreate() is not called, while its onCreateView() is.
So, you may want to do something like this:
public class MyFragment extends Fragment {
int mBackStackSize = 0;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBackStackSize = getFragmentManager().getBackStackEntryCount();
}
public boolean isRestoredFromBackstack() {
return mBackStackSize > getFragmentManager().getBackStackEntryCount();
}
}
If you added fragment to backstack, and after some manipulation you hide it using fragmentTransaction.hide(fragment) and then restore it from backstack like fragmentTransaction.show(fragmentManager.findFragmentByTag(fragment.getName())); you can override onHiddenChanged(boolean hidden)
#Override
public void onHiddenChanged(boolean hidden) {
// TODO Auto-generated method stub
super.onHiddenChanged(hidden);
if (!hidden) {
//fragment became visible
//your code here
}
}
In some cases you can use isVisible method to understand is it first showing of a fragment or is it restored from the backstack.
Related
I was wondering, what is the Fragment lifecycle methods, I should commit FragmentTransaction to avoid famous
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
According to http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html, it gives great tip, on how to avoid such exception, by commit FragmentTransaction
FragmentActivity
onCreate()
onResumeFragments()
onPostResume()
Fragment
???
However, how about Fragment? What is the suitable Fragment lifecycle we should commit our fragment? For instance, under very rare situation, I will get exception from Google Play Console crash report, while trying to commit Fragment in another Fragment's onCreate.
public class BuyPortfolioFragment extends Fragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final FragmentManager fm = this.getFragmentManager();
// Check to see if we have retained the worker fragment.
this.statusBarUpdaterFragment = (StatusBarUpdaterFragment)fm.findFragmentByTag(STATUS_BAR_UPDATER_FRAGMENT);
if (this.statusBarUpdaterFragment == null) {
this.statusBarUpdaterFragment = StatusBarUpdaterFragment.newInstance();
this.statusBarUpdaterFragment.setTargetFragment(this, 0);
// java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
fm.beginTransaction().add(statusBarUpdaterFragment, STATUS_BAR_UPDATER_FRAGMENT).commit();
} else {
statusBarUpdaterFragment.setTargetFragment(this, 0);
}
p/s I know I can avoid such exception by using commitAllowingStateLoss. I want to use it as last resource.
Fragment's lifecycle state not always matches Activity's. Fragment's method getFragmentManager() returns the FragmentManager of it's hosting Activity (unless it's a child Fragment, if so this method returns the child fragment manager of a hosting Fragment). You may never know in which state is Fragment's hosting Activity unless you make tracking code. So it's really possible that the transaction eventually may be committed after Activity onSaveInstanceState() was called.
I suggest using getChildFragmentManager() and deal with child fragments from fragments.
Or if your intention was really to control Activity Fragments, make accessors for controlling it's state, like
// Activity method
public void showSomeFragment() {
if (mFragmentTransactionsAllowed) {
// do transaction
}
}
// And track the boolean
#Override
protected void onCreate(Bundle b) {
super.onCreate(b);
// override on onCreate() in case if Activity object is reused and state was true
mFragmentTransactionsAllowed = true;
}
#Override
protected void onStart() {
super.onStart();
// override here so that if activity goes foreground but not yet destroyed
mFragmentTransactionsAllowed = true;
}
#Override
protected void onResume() {
super.onResume();
mFragmentTransactionsAllowed = true;
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mFragmentTransactionsAllowed = false;
}
My code:
public class MainActivity extends AppCompatActivity {
private FragmentA fragmentA;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
fragmentA = FragmentA.newInstance();
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragment_a_container, fragmentA, "FRAGMENT_A");
fragmentTransaction.commit();
}
else {
fragmentA = (FragmentA) getSupportFragmentManager().findFragmentByTag("FRAGMENT_A");
}
}
}
I don't really know what I am doing but this is currently what I do. I define a container for the Fragment and then I use a FragmentTransaction to replace it with a Fragment. The part I am confused about though is the else statement.
Should I be structuring this differently?
I thought configuration changes wiped out Activities and Fragments so why check for the Fragment in some support manager? Does this mean Fragments don't actually get destroyed? At the same time, they DO seem to get destroyed because they appear to reset unless I use onSaveInstanceState or the getArguments() approach.
Edit: What's wrong with doing this:
public class MainActivity extends AppCompatActivity {
private FragmentA fragmentA;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragmentA = FragmentA.newInstance();
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragment_a_container, fragmentA, "FRAGMENT_A");
fragmentTransaction.commit();
}
}
They do get destroyed and recreated for you on configuration changes by the, in this case, SupportFragmentManager.
To answer your questions:
Should I be structuring this differently?
No, that's exactly how you should create fragments if there is no saved state and retrieve them when there is. See also my answer here;
a) so why check for the Fragment in some support manager?
Because the manager handles the lifecyle of the fragment for you when there is a configuration change.
b) Does this mean Fragments don't actually get destroyed?
No, it does get destroyed. See this diagram for a reference.
Edit to answer some of your questions from the comments:
But any member variables inside that Fragment are completely lost on configuration change unless I save them in that Fragment's onSaveInstanceState, right?
That is correct. Because your fragment is being destroyed, everything not being saved on onSaveInstanceState gets lost.
So then what exactly am I restoring?
You are not restoring anything. You are only retrieving the reference to the fragment that was previously created. You restore your variables on the onRestoreInstanceState() method of your fragment.
What's wrong with doing this (the code from the edit in the question)?
If you do that, you are adding a new fragment instance to the R.id.fragment_a_container container. So the old fragment will get lost together with the state of it you saved on onSaveInstanceState(). It will be a new fragment, with new information in it and the event onRestoreInstanceState() won't be called for it.
I have implemented a simple activity with this code:
public class MainActivity extends AppCompatActivity implements Fragment_1.Operations{
Fragment_1 fragment_1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragmentactivity);
fragment_1=(Fragment_1)getSupportFragmentManager().findFragmentByTag("fragment_1");
}
//called on buttonclick, fired from a button existing in R.layout.fragmentactivity
public void createFragment(View view){
if (getSupportFragmentManager().findFragmentByTag("fragment_1")==null){
fragment_1=new Fragment_1();
FragmentTransaction transaction=getSupportFragmentManager().beginTransaction();
transaction.add(R.id.fragmentactt,fragment_1,"fragment_1");
transaction.commit();
getSupportFragmentManager().executePendingTransactions();
}
else{
fragment_1=(Fragment_1)getSupportFragmentManager().findFragmentByTag("fragment_1");
}
//Simply adding item to the listview contained in fragment_1.
fragment_1.add("Project #1");
fragment_1.add("Project #2");
fragment_1.add("Project #3");
}
//callback of interface "Operations"
#Override
public void buttonClicked() {
FragmentTransaction transaction=getSupportFragmentManager().beginTransaction();
if (fragment_1.isAdded()){
transaction.remove(fragment_1);
//transaction.addToBackStack(null);
transaction.commit();
getSupportFragmentManager().executePendingTransactions();
}
}
}
Well, the doubt came from the fact that no "onSaveInstanceState" needed to be implemented, everything got "saved" without any problems.
So, why should i use putfragment and getFragment? Do these methods need to be called in order to avoid that, when OS kills app process, they would be lost? This is the only reason i can imagine to force onSaveInstanceState and onRestoreInstanceState methods.
Any help is appreciated.
Activity and fragment lifecycles are linked so when any callback method such as onResume is called for the activity, it is called for the fragment too.
putFragment and getFragment help the activity to manage its fragment child's lifecycle. The activity also has to save instance state.
In order to be activity independant, a fragment can manage his own instance state.
I have ActivityA attaching FragmentA. There's an EditText in FragmentA which, if focused, adds FragmentB (below). The stack trace starts with onDestroy in ActivityA, which triggers onFocusChange, which fires off popBackStack. The isRemovingOrPartOfRemovalChain() should be returning true at this point but it occasionally returns false causing the popBackStack, hence the exception. Is there a bug in that method?
editText.setOnFocusChangeListener(new OnFocusChangeListener(){
#Override
public void onFocusChange(View view, boolean hasFocus) {
if(hasFocus){
FragmentManager fragmentManager = getChildFragmentManager();
Fragment fragment = fragmentManager.findFragmentByTag(FRAGMENT_B);
if(fragment == null){
FragmentB fragmentB = FragmentB.newInstance();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.fragment_b, fragmentB, FRAGMENT_B);
fragmentExploreSearchListTransaction.addToBackStack(null);
fragmentExploreSearchListTransaction.commit();
}
else{
if(!isRemovingOrPartOfRemovalChain()){
getChildFragmentManager().popBackStack();
}
}
}
});
public boolean isRemovingOrPartOfRemovalChain(){
if(isRemoving()){
return true;
}
Fragment fragment = this.getParentFragment();
if(fragment != null){
if(((MainFragment) fragment).isRemovingOrPartOfRemovalChain()){
return true;
}
else{
return false;
}
}
else{
return(getActivity().isFinishing());
}
}
/**
* Return true if this fragment is currently being removed from its
* activity. This is <em>not</em> whether its activity is finishing, but
* rather whether it is in the process of being removed from its activity.
*/
final public boolean isRemoving() {
return mRemoving;
}
When you commit fragment after onSavedInstanceState(Bundle outState) callback (e.g. onDestroy()), the committable Fragment state will be lost (becase Fragment.onSaveInstanceState(Bundle) won't be called in this situation).
In this case, when Activity is recreated, the committed fragment will not be present in Activity's FragmentManager, and so will not be restored. This is considered a state loss.
This behaviour might break or corrupt user experience, and is considered unintentional and exceptional, so the Android framework warns you about that by throwing an exception :-) Better save than sorry, right?
In case one knows what one's doing (which is almost always not so :-)), one may stick with .commitAllowingStateLoss(), but I strongly advise against it, as it will bring a legal bughole into your application.
Just do not commit fragments after it has became known that your Activity is destroying: for example,
...
boolean fieldActivityIsDestroying;
....
public void onSaveInstanceState(Bundle out){
super.onSaveInstanceState(out);
fieldActivityIsDestroying = true;
}
and check for field value when commiting the fragment.
Also you might want to FragmentManager.executePendingTransactions() to perform any fragment transactions immediately after you commit them to manager (default commit is asynchronous).
your issue because of activity state lost.
try this
fragmentExploreSearchListTransaction.commit()
to
fragmentExploreSearchListTransaction.commitAllowingStateLoss()
but it is not good solution, so i refer you read this blog, this blog is about fragment Transaction after save Activity Instance, I hope my information will help you.
There is not enough code to provide with the complete solution, but in this case:
It's better use Fragment#isRemoving() to check if fragment removed from activity;
If focus changed expected from user interaction with the screen it's better set/remove listener at following methods:
onCreateView()/onDestroyView();
onResume()/onPause();
If there is any reason that this solutions not work feel free to clarify.
Good luck!
Why don't you just set the onFocusedChangeListener in OnResume() and remove it in OnPause()?
That should prevent it from being triggered when your activity is finishing.
When orientation changes, fragment viewState restored only in onStart.
After onAttach, onCreateView, onViewCreated and onActivityCreated and even after onCreate.
Why? This is too late.
I need to populate db query results to ListView based on some TextView value. Currently i try to do this in onViewCreated. But view state isn't restored at this step.
Can i force restore early?
Or how to overcome this problem?
Any ideas, please.
PS: i use actionbarsherlock and dependent android support-v4 r7 library
PS2: if i will load data in onStart then it will do additional queries when fragment is resumed after onStop (i can solve this by adding some boolean isLoaded - but this isn't best solution).
In Android API >= 17 (Android 4.2 Jelly Beans) there is a method:
public void onViewStateRestored (Bundle savedInstanceState)
which is called before onStart() and after onActivityCreated() as mentioned in docs.
In Android API < 17 there is no such method. But there are two solutions:
Don't rely on view state while initializing Fragment and save all required initialization state as Fragment state (i.e. override Fragment#onSaveInstanceState()). Later you can restore fragment state in onCreate(), onCreateView() or onViewCreated().
Perform initialization in onStart() as specified in question.
[EDIT 1 - - - - - - -]
// Check to see if the Fragment back stack has been populated
// If not, create and populate the layout.
// so your fragment wont recreated
YourFragment yourFragment = (YourFragment )fm.findFragmentById(R.id.fragment_container);
if (yourFragment == null) {
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment_container, new YourFragment ());
ft.commit();
}
[EDIT 1 - - - - - - -]
/**
* Listing 4-4: Fragment skeleton code
* Listing 4-5: Fragment lifecycle event handlers
*/
package com.paad.fragments;
import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class MySkeletonFragment extends Fragment {
// Called when the Fragment is attached to its parent Activity.
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// Get a reference to the parent Activity.
}
// Called to do the initial creation of the Fragment.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Initialize the Fragment.
}
// Called once the Fragment has been created in order for it to
// create its user interface.
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
// Create, or inflate the Fragment's UI, and return it.
// If this Fragment has no UI then return null.
return inflater.inflate(R.layout.my_fragment, container, false);
}
// Called once the parent Activity and the Fragment's UI have
// been created.
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Complete the Fragment initialization Ğ particularly anything
// that requires the parent Activity to be initialized or the
// Fragment's view to be fully inflated.
}
// Called at the start of the visible lifetime.
#Override
public void onStart(){
super.onStart();
// Apply any required UI change now that the Fragment is visible.
}
// Called at the start of the active lifetime.
#Override
public void onResume(){
super.onResume();
// Resume any paused UI updates, threads, or processes required
// by the Fragment but suspended when it became inactive.
}
// Called at the end of the active lifetime.
#Override
public void onPause(){
// Suspend UI updates, threads, or CPU intensive processes
// that don't need to be updated when the Activity isn't
// the active foreground activity.
// Persist all edits or state changes
// as after this call the process is likely to be killed.
super.onPause();
}
// Called to save UI state changes at the
// end of the active lifecycle.
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save UI state changes to the savedInstanceState.
// This bundle will be passed to onCreate, onCreateView, and
// onCreateView if the parent Activity is killed and restarted.
super.onSaveInstanceState(savedInstanceState);
}
// Called at the end of the visible lifetime.
#Override
public void onStop(){
// Suspend remaining UI updates, threads, or processing
// that aren't required when the Fragment isn't visible.
super.onStop();
}
// Called when the Fragment's View has been detached.
#Override
public void onDestroyView() {
// Clean up resources related to the View.
super.onDestroyView();
}
// Called at the end of the full lifetime.
#Override
public void onDestroy(){
// Clean up any resources including ending threads,
// closing database connections etc.
super.onDestroy();
}
// Called when the Fragment has been detached from its parent Activity.
#Override
public void onDetach() {
super.onDetach();
}
}
source : Professional Android Development 4 - Reto Meier
You can always handle orientation changes yourself with onConfigurationChanged(). See a nice example here http://alexfu.tumblr.com/post/13926762386/android-dev-handling-fragment-recreation-manually