Activity retain instance of Fragment on onSaveInstance - android

I'm writing an application where using onSaveInstance(..) to retain values on device config change(say device font, local).
Here application Activity using multiple Fragment to display. After chnage in config change when coming back to app then onCreate(..) execute and app checked if Bundle object is null. Now till this state app didn't set any Fragment child back but but still last set Fragment (before change in config) child started to execute it's life cycle method.
How can prevent it, one way to check Same Bundle object from Fragment child, same as Activity and return. But is there other way to remove child from Activity on device-config change!
Activity reference code to handle re-calling:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.reading_activity);
if(savedInstanceState == null) {
init(savedInstanceState);
} else {
// Don't do anything
}
}
Here init(..) responsible to set Fragment child.

Related

Viewpager fragment reference to new copy after activity restore

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();
}

Android change fragment of navigation view

I have an Android app with 1 base activity and a few fragments. They can be changed using the NavigationView inside the DrawerLayout. Users can change the language of the application in one of the fragments and when I relaunch the application, I want users to go back to that specific fragment.
=====DrawerLayout=====
1. Fragment Home -> This is the starting fragment
2. Fragment One
3. Fragment Settings -> Users change the language here.
When users change the language, a method in the base activity is called and I change the Locale, and call recreate(). This will refresh the app with the Fragment Home being displayed in the new language. I want to programatically change to Fragment Settings.
navigationView.<METHOD?>
In order to solve your problem, you can save the special state and recreate the activity. When the activity is recreated, it will know that it needs to move to the settings page using the saved state.
Try this in your activity:
static final String SHOW_SETTINGS = "SHOW_SETTINGS";
private boolean showSettings = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
createLayoutAndDoOtherOnCreateThings();
// Check whether we're recreating a previously destroyed instance
if (savedInstanceState != null) {
// Restore value of members from saved state
if (savedInstanceState.getBoolean(SHOW_SETTINGS, false)) {
showSettingsFragment();
}
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putBoolean(SHOW_SETTINGS, showSettings);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
private void callRecreateWithSettingsWhenRecreating() {
showSettings = true;
recreate();
}

Why do you check for savedInstanceState == null when adding fragment?

In the fragment doc, in one of the example, they check for savedInstanceState == null when adding a fragment:
public static class DetailsActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE) {
// If the screen is now in landscape mode, we can show the
// dialog in-line with the list so we don't need this activity.
finish();
return;
}
if (savedInstanceState == null) {
// During initial setup, plug in the details fragment.
DetailsFragment details = new DetailsFragment();
details.setArguments(getIntent().getExtras());
getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
}
}
}
What is the purpose of this check? What would happen if it is not there?
What is the purpose of this check?
To not add the fragment twice, though I prefer checking to see if the fragment is there instead of relying on that Bundle being null.
What would happen if it is not there?
Initially, nothing, as the Bundle will be null when the activity is first created.
However, then, the user rotates the device's screen from portrait to landscape. Or, the user changes languages. Or, the user puts the device into a manufacturer-supplied car dock. Or, the user does any other configuration change.
Your activity will be destroyed and recreated by default. Your fragments will also be destroyed and recreated by default (exception: those on which setRetainInstance(true) are called, which are detached from the old activity and attached to the new one).
So, the second time the activity is created -- the instance created as a result of the configuration change -- your fragment already exists, as it was either recreated or retained. You don't want a second instance of that fragment (usually), and therefore you take steps to detect that this has occurred and not run a fresh FragmentTransaction.

onCreate being called on Activity A in up navigation

So I have an Activity A and an Activity B. I want Activity A to be able to navigate to Activity B with the press of a button. That works, but when I use the up navigation(the home button in the action bar) to navigate back to Activity A, onCreate() is called again and the old information that the user typed in is lost.
I've seen: onCreate always called if navigating back with intent, but they used Fragments, and I'm hoping not to have to redesign the entire app to use fragments. Is there any way I can stop onCreate() from being called every time Activity A becomes active again?
This behavior is totally fine and wanted.
The system might decide to stop Activities which are in background to free some memory.
The same thing happens, when e.g. rotating the device.
Normally you save your instance state (like entered text and stuff) to a bundle and fetch these values from the bundle when the Activity is recreated.
Here is some standard code I use:
private EditText mSomeUserInput;
private int mSomeExampleField;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// TODO inflate layout and stuff
mSomeUserInput = (EditText) findViewById(R.id.some_view_id);
if (savedInstanceState == null) {
// TODO instanciate default values
mSomeExampleField = 42;
} else {
// TODO read instance state from savedInstanceState
// and set values to views and private fields
mSomeUserInput.setText(savedInstanceState.getString("mSomeUserInput"));
mSomeExampleField = savedInstanceState.getInt("mSomeExampleField");
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// TODO save your instance to outState
outState.putString("mSomeUserInput", mSomeUserInput.getText().toString());
outState.putInt("mSomeExampleField", mSomeExampleField);
}
You can make the up button behave like pressing back, by overriding onSupportNavigateUp()
#Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
If you want to navigate from child to parent without recreating the parent (calling onCreate method), you may set the android:launchMode="singleTop" attribute for the parent activity in your AndroidManifest.xml

How to persist fragment data after backstack transactions?

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.

Categories

Resources