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
Related
When my activity containing viewpager is killed by system in background and then restores its state, fragments are correctly created and viewpager adapter can also point to them correctly.
But when I get a fragment reference and try to access its fields, they are all null (checked by using breakpoint).
I checked this by placing breakpoints in fragment onCreateView() and in my activity button's clickListener.
((WelcomeFragment)homeActivityFragmentPageAdapter.getItem(POSITION_HOME)).setdata(myData);
Now this method will through null pointer exception since setdata(data) is internally accessing arraylist field of fragment.
This creates a problem for me since, my activity has to continuously feed network data to the fragment by calling its public method (as suggested by documentation).
How to insure that after state restored; correct instance is pointed in my activity.
Try to use instantiateItem adapter method instead getItem.
((WelcomeFragment)homeActivityFragmentPageAdapter.instantiateItem(mViewPager, POSITION_HOME)).setdata(myData);
Method getItem is overrided method, and common use is creation of child fragments.
EDIT:
In case of the question's scenario, you also need to store the state of FragmentStatePagerAdapter manually:
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState
.putParcelable("pages",homeActivityFragmentPageAdapter.saveState());
super.onSaveInstanceState(savedInstanceState);
}
Then you can retrieve the state in oncreate:
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
homeActivityFragmentPageAdapter.restoreState(savedInstanceState.getParcelable("pages"),this.getClassLoader());
welcomeFragment = (WelcomeFragment) homeActivityFragmentPageAdapter.instantiateItem(mViewPager, POSITION_HOME);
}
else { //simply create a new instance here}
homeActivityFragmentPageAdapter.addFragmentToAdapter(welcomeFragment);
homeActivityFragmentPageAdapter.notifyDataSetChanged();
}
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;
}
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 Activity with a FragmentTabHost. I add the fragments to the tab using the following code:
mTabHost.addTab(mTabHost.newTabSpec(tab1Name).setIndicator(tabIndicator1),
EventSettingsStep1Fragment.class, null);
mTabHost.addTab(mTabHost.newTabSpec(tab2Name).setIndicator(tabIndicator2),
EventSettingsStep2Fragment.class, null);
When I switch to different tabs, I'd like to retain all the values (view state, etc) so that I have the same data when I switch back to the tab.
I overrode the onSaveInstanceState method & in there, I added values that I want retained to the bundle.
I ran through the methods being called and I have the following:
Switching from Tab1 to Tab2: Tab1:onPause then Tab2:onCreateView, Tab2:onResume
Switching from Tab2 to Tab1: Tab2:onPause then Tab1:onCreateView, Tab1:onResume
onSaveInstanceState is not being called.
Here is the code for one of my fragments:
public class EventSettingsStep1Fragment extends Fragment implements View.OnClickListener {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d(TAG, "onCreateView");
if (savedInstanceState != null) {
Log.d(TAG, "restoring onSavedInstanceState");
Gson gson = new Gson();
event = gson.fromJson(savedInstanceState.getString("event"), Event.class);
}
if (event != null) {
//set views
}
return v;
}
#Override
public void onResume() {
super.onResume();
Log.d(TAG, "onResume");
}
#Override
public void onPause() {
super.onPause();
Log.d(TAG, "onPause");
}
#Override
public void onSaveInstanceState(Bundle outState) {
Log.d(TAG, "onSaveInstanceState");
super.onSaveInstanceState(outState);
Gson gson = new Gson();
outState.putString("event", gson.toJson(event));
}
}
Why is onSaveInstanceState not being called? Is it only triggered through the FragmentActivity?
onSaveInstanceState is not being called because the framework simply reuses the already-existing instance of the fragment. onSaveInstanceState only gets called when the instance is about to be destroyed and then recreated. This happens for example when you rotate the display and force the hosting activity to be recreated.
onSaveInstanceState is also not called when you push a fragment on the backstack of a FragmentManager. You will have to restore the state from the already existing instance, which can be very annoying. See SO questions How can I maintain fragment state when added to the back stack? and Once for all, how to correctly save instance state of Fragments in back stack? for example.
Basically you will have to do what the answers to these questions suggest: continue using the values of your instance variables and do not rely on a saved instance state.
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.