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.
Related
I've got an activity with a retained fragment. This fragment handles DB queries and sends the results back to the activity via an interface.
When I rotate the device, the activity is destroyed and re-creates itself as expected. It also re-connects to the retained fragment (which wasn't destroyed) which is continuing to handle the DB queries despite the device rotation.
My problem is when the retained fragment gets the DB queries result back, it tries to send these via the interface to the activity. But, if the device has been rotated, the activity could be in the destroyed state (and not yet re-created) so the fragment can't send the results to the activity.
When the activity is eventually re-created, it's missed "it's chance" to receive the interface calls from the fragment and get the DB results. How to solve this?
Just a bit more detail - the activity has a button which the user presses to start the retained fragment doing the DB queries (they don't just start automatically when the activity is created or the activity attached to the fragment).
Activity.onCreate()
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
// Get our Retained Fragment if already exists, otherwise create a new one
FragmentManager fragManager = getSupportFragmentManager();
mRetFrag = (QueryFragment) fragManager.findFragmentByTag(QueryFragment.FRAGMENT_TAG);
if (mRetFrag == null) {
// First time - create a new retained fragment
mRetFrag = QueryFragment.newInstance();
fragManager.beginTransaction().add(mRetFrag, QueryFragment.FRAGMENT_TAG).commit();
}
// Button to start DB querying in fragment
Button queryBtn = (Button) findViewById(R.id.query_button);
queryBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mRetFrag.runDBQueries();
}
});
}
And in the fragment when it has the DB query results, I try to send this back to the Activity. So, mCallbackListener could be null if the activity isn't yet created.
// Pass the data to the activity
if (mCallbackListener != null) {
mCallbackListener.onQueryFinished(data);
}
Your mCallbackListener points to an activity, which doesn't exist anymore, as long as it has been destroyed after rotation.
You have to get another instance of your activity:
if(null == mCallbackListener) {
mCallbackListener = (MainActivity) getActivity();
}
Another solution is to use event bus like Greenrobot's EventBus or Otto.
Here's nice blog about how to use Otto.
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.
I am trying to get a better understanding of FragmentManager and FragmentTransactions to properly develop my application. It is specifically in regards to their lifecycle, and the long-term effect of committing a FragmentTransaction(add). The reason I have a confusion over it is when I ran a sample Activity, listed at the end of the post.
I purposely created a static FragmentManager variable called fragMan and initially set it to null. It is then checked against in onCreate() if it is null and when null value is seen, the fragMan variable is set to the getFragmentManager() return value. During a configuration change, the Log.d showed that fragma was not null, but the Fragment "CameraFragment" previously added was not found in fragman and the fragman.isDestroyed() returned true. This to me meant that the Activity.getFragmentManager() returns a new instance of a FragmentManager, and that the old FragmentManager instance in fragMan had its data wiped(?)
Here is where the confusion comes in.
1) How is "CameraFragment" still associated in the Activity on a configuration change and is found in
the new instance of FragmentManager?
2) When I hit the back button on my phone to exit the Activity, I then relaunched the sample
Activity using the Apps menu. The CameraFragment was not visible anymore, and the
onCreate() check revealed that fragMan was still not null. I thought that hitting the back button
called the default finish() command, clearing the Activity from memory and that restarting it
would produce the same result as the initial launch of the sample Activity?
Thank you for any and all help you can provide!
public class MainActivity extends Activity
{
static FragmentManager fragMan = null;
FragmentTransaction fragTransaction;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (fragMan != null)
{
Log.d("log", Boolean.toString(fragMan.isDestroyed()));
if(fragMan.findFragmentByTag("Camera Preview") == null)
{
Log.d("log", "Camera Preview not found.");
}
}
else
{
fragMan = getFragmentManager();
fragTransaction = fragMan.beginTransaction();
Fragment cameraFragment = new CameraFragment();
ViewGroup root_view = (ViewGroup) findViewById(android.R.id.content);
fragTransaction.add(root_view.getId(), cameraFragment, "Camera Preview");
fragTransaction.commit();
}
Static variables in Java are kept across Activity creation/destruction - they are associated with the class itself but not a particular instance of the class.
See the official documentation here:
http://docs.oracle.com/javase/tutorial/java/javaOO/classvars.html
Your application doesn't end when the user returns to the home screen, it just gets put in a background state. If you force stopped the application and restarted it, then the static FragmentManager will be null.
With regards to CameraFragment, unless you've set setRetainInstance(true), it will get destroyed on an orientation change.
==== EDIT
Here's a more detailed flow of what's happening...
You open the application up for the first time
Activity, say instance A1, gets created and its corresponding FragmentManager instance, FM1, also gets created
You store FM1 as a static variable
You go back to home
Activity A1 and FM1 gets destroyed because of the normal Activity lifecycle, although FM1's reference is still held onto by the static variable. At this point, FM1 loses all the fragments it contains and isDestroyed() will return true.
Starting the app again
New Activity instance A2 gets created along with its new FragmentManager instance FM2
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'm writing an app where I'm using fragments, and I'm seeing a really confusing behavior in my DDMS heap dumps: When I initially run my application, let's say there are a couple of fragments in the stack:
HomeFragment: 1
WelcomeFragment: 1
SignInFragment: 1
CreateAcctFragment: 1
The immediate dominator for each of these is .
Then I rotate the screen. I now get:
HomeFragment: 2
WelcomeFragment: 2
SignInFragment: 2
CreateAcctFragment: 2
Now the immediate dominators for each instance are for one and android.support.v4.app.FragmentManagerImpl for the other.
I initially thought I had a memory leak, that one of my fragments was not being garbage collected, as that had happened to me before. However, in that case every time I rotated the screen I added one more instance of each. Now no matter how many times I rotate the screen, I "only" see 2 instances of each.
Any thoughts/suggestions?
You should only instantiate the fragments if your activity onCreate() method is not passed a bundle. The android framework will instantiate them for you if a bundle is passed to onCreate().
This is the example of Activity.onCreate from fragment docs:
http://developer.android.com/guide/topics/fundamentals/fragments.html
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();
}
}
}