I'm writing an app that loads user profiles and user's ratings for different places. The app uses fragments pervasively, and it's relatively easy to jump from a profile to a rated place.
As a user clicks a profile and gets to a rated place, they can click another profile on a rated place and go on and on.
The problem I'm having is memory related, when I'm looking at a ranked place and I click a profile, I switch from one Activity to the next. In both of these activities, after setContentView I load a fragment dynamically into layout space.
Now, as I shuttle between these activities, onSaveInstance state is almost always called, however since the Fragment displaying whatever was in the foreground before the activity switch, onDestroyView is not called.
What I would like is when onSaveInstanceState is called in these dynamic fragments, it to force onDestroyView to be called as well.
The only time onDestroyView seems to be called is when I add a Fragment to the back stack. When another activity comes to the foreground and this fragment is stopped, I'd like on DestroyView to be called as well.
The current workaround I want to implement is have an empty fragment with no view, and every time I call startActivity(Intent i), load this dummy fragment to destroy views and start the next activity. Then, when I come back, pop it off the back stack and restore the actual last fragment.
This however seems excessive. So, for a stopped fragment in a stopped activity with a new activity in front of it, how do I force it to destroy it's View?
First, you should not force or satisfy onDestroyView to fix your code, that's the job of the FragmentManager and the Android lifecyle # Pausing and Resuming an Activity. If you want to work with your existing code, use the other override methods onPause() or onStop().
Without posted code, I assume you're using the replace() method to display one fragment over another. This more or less forces you to manage the fragments yourself, some developers actually succeed in doing so with some struggle (look at other SO questions).
So my suggestion for you is either:
Maintain your own states, and show the proper fragments based on the state.
Use the BackStack and let the Fragment management handle the stack/states.
Related
I have a Xamarin Android ActionBar-based app. I am not using anybody's compat library (Sherlock, etc.) I have a fragment for each tab. I have noticed the following behavior:
Suppose the user starts on Tab A, with corresponding fragment A. The user the switches to tab B, with corresponding fragment B. My code tells the app to detach fragment A, and attach fragment B. So far, so good. Now the weird part.
Suppose the user rotates the device. The fragments have all set
this.RetainInstance = true;
And I keep static references to them. So they stick around, which is what I want. But the weird thing is that when the activity is re-created after rotation, both fragments A and B get attached by Android, during my
base.OnCreate()
call from MyActivity.OnCreate(). This results in the app doing work which I assume is not needed. So my question is, how can I prevent that from happening?
You should read android guidelines. You must save the fragment state and the activity state and i suppose do not use static links and retainInstanceState. Google how to use Bundle and saveInstanceState, onCreate onRestoreInstancestate methods. Also after a recreation you can find you fragment by placed tag and reuse it without replace.
http://developer.android.com/training/basics/activity-lifecycle/recreating.html
http://developer.android.com/guide/components/fragments.html
I have two activities A and B. The A has a ListFragment which uses LoaderManager, whereas B activity shows a details about the item selected in the A's ListFragment. I've just noticed that when I use a back button to get from the B back to the A, the position in the ListFragment preserve, but when I use the up button (left-point caret) in the action bar, the A activity is recreated and thus position in list view is lost.
I would like fix this issue, but I am not sure about the best way how to do it right.
I come up with this solutions:
a) Use onBackPressed()
Replace the default implementation for the android.R.id.home (the up action bar button) in the B activity, and instead of the NavUtils.navigateUpFromSameTask(this) function call the onBackPressed() activity method. I've tested it and it works.
b) Keep use NavUtils.navigateUpFromSameTask(this)
But implement the onSaveInstanceState and restore listView position during onCreate method of the ListFragment used by the A activity. (I've not tested this approach yet)
Which of this solutions is better? Or is there any other (much more better) solution?
Solution a) is pretty simple and straight forward, but b) is probably better because the default implementation of the up caret is used.
Any ideas are welcome. Thanks.
Solution c is the correct option. First, though, an explanation of the problem with solution a.
There is absolutely no point in having two back buttons in your Activity. Furthermore, option a actually breaks the up button. The point of the up button is to provide a way for users to stay within your app when they have landed in your app from an outside source. For example, if you land on activity B from an outside activity C and if you are using your option a, then pressing "up" in activity B will result in activity C being shown. The behavior you would want would be for activity A to be shown.
As you can see, solution b is on the right track. You definitely want to go up to A and not back to C. However, simply storing the state in onSaveInstanceState will not cause the state to be retained. This is because onSaveInstanceState only gets called if your application may be killed by the system. It is not guaranteed to be called if your application was destroyed manually, and it certainly won't be called when a new instance of your Activity is created. If the Intent starts a new activity, then it will not have its state restored from the other activity.
There solution, then, is that you must write anything persistent to a shared preference file (or a custom persistent alternative). When doing this you can guarantee that all instances of an Activity share the same state across multiple tasks so long as their onResume (or wherever you restore state) is called. OR:
If you know exactly how you want your navigation to work, you can avoid writing everything to persistent state by using a combination of Intent flags and Activity task affinities. If you want to use the same activity as up even if you navigate into the application from an outside source, then you can leave your Activity A's affinity as default (linked to the application) and use something like Intent.FLAG_ACTIVITY_CLEAR_TOP.
Personally, I'd try the Intent flag approach first and failing that fall back to writing the state persistently. You just don't really want scroll location sitting on persistent storage if you can avoid it..
check out this presentation: https://speakerdeck.com/jgilfelt/this-way-up-implementing-effective-navigation-on-android. It answers to all of your problems.
I'm trying to create a fragment that exists for the entire duration of my app's lifecycle. I want it to be created only once and to be destroyed when the activity's onDestroy() function is called (so, ideally, never...). I understand that this goes against what android intended when it introduced fragments, but the nature of my project makes changing this requirement impossible.
What I would like clarification on, is this whole backstack business. I am slightly confused about what exactly the backstack represents, I understand that it is a stack of previous UI states, but does that mean it is a stack of the fragments that the user has currently iterated through? Or is this a stack of FragmentTransactions and when you popBackStack(), it "undo's" the last FragmentTransaction that was committed (and if so, what does it mean to "undo" a FragmentTransaction...does it just remove an added fragment and add a removed fragment, what if I want it to detach a fragment and not remove it when popBackStack() is called?)?
Finally, does calling detach() prevent Android from killing my Fragment (unlike remove() and replace() which will immediately call the onPause(), onStop(), onDestroy(), onDestroyView() sequence)? If so, how do I get popBackStack() to detach() my Fragment (can I even do this?)...should even be adding it to the backstack?
Further restraints on my project - I want all other fragments to behave normally (i.e. they should be able to be destroyed/created at will by the OS). This fragment will not open another fragment, but if I hit the back button, I want it to return to whatever fragment opened it (without destroying the fragment).
Any guidance and/or clarification on the issues I enumerated above would be greatly appreciated!! Thanks in advance!
Referring to the Fragment documentation, specifically Performing Fragment Transactions it states:
Whereas, if you do call addToBackStack() when removing a fragment, then the fragment is stopped and will be resumed if the user navigates back.
I want to understand the implications of a Frgament being stopped because I am creating several Fragments which I would like to place on the BackStack yet must still interact with them from an external (i.e. non-GUI) thread, to set a flag within the Fragment for example. Also if the Fragment starts threads within it, are they too stopped or 'suspended' once the Fragment is placed onto the backstack?
Its possible to retain a Fragment between Activities?
Lets say I have Activity A with Fragment F_Left placed at the left and Fragment F_Right placed at the right. If I want to launch a new Activity and keep Fragment F_Left... how can I do it?
Can I retain Fragment F_Left state between activities?
Note that I want to launch a new Activity because Fragment F_Left is my app menu and Fragment F_Right changes completely the context of the user operations... and my app have many of operations, so it makes sense to have an Activity per operation.
I know its possible to retain Fragment within an Activity, but as Fragment life cycle is closely tied to the container Activity I don't know if this is possible keep Fragment state between Activities.
Since API Level 13 (HONEYCOMB_MR2, June 2011), you can save and restore the state of a fragment across activities.
To save the state, use FragmentManager.saveFragmentInstanceState(), providing a reference to the Fragment whose state you wish to save. The Fragment must be attached at the time you attempt to save its state.
To restore the state, use Fragment.setInitialSavedState() with the return value when you instenciate the same Fragment.
myFragment = new MyFragment();
myFragment.setInitialSavedState(appState.getMyFragmentState());
fragmentManager.beginTransaction().add(R.id.container, myFragment).commit();
You can persist the SavedState object across activities as you would any other object; one way is to subclass Application as shown above (appState is the instance of our subclass).
Based on your response to my comment, I have a slightly different answer. It may not end up being the best answer in your specific situation, I'll let you decide that. :)
Right now you are bundling your fragments in activities because that is what made sense to you, but really, you can probably treat the entire process as one activity and use fragment transactions to hide & show (or create and destroy) fragments as needed.
Since you won't be creating and destroying activities, your menu fragment on the left will be left untouched, and you won't have any problems with its UI state. The set of operations you want to run (which no doubt includes all sorts of different fragments on the right) does not need to be launched in a new activity - but you will have to find a way to manage the logic you need for the fragment transactions (either in your one über-activity or in some kind of OperationsManager class).
I think this will end up being a lot smoother for the users of your application since the single activity just remains running - and you are only changing the parts that actually need to change.
If I want to launch a new Activity and keep Fragment F_Left... how can I do it?
Don't launch a new activity.
Can I retain Fragment F_Left state between activities?
Not automatically. It is not the same fragment. You would pass data between the activities for use by the fragment no differently than you would without any fragments at all.
To potentially answer your original question, if you fire off another activity then I believe that you can save your fragment from your first activity by calling FragmentManager::putFragment(...) when onSaveInstanceState(...) is called and then getting it back later, e.g. in onCreate(...).
However, I have to agree with Mark D's response.
Incidentally I'm doing something similar in that I have a dual pane setup whereby the left pane if fixed with a number of options with each option invoking a different fragment in the right pane. Furthermore selecting an entry in the right pane can result in the right fragment being replaced by another one.
However, I have taken the approach whereby by left fragment is only responsible for displaying and handling responses from the immediate fragment which appears in the right hand pane. Furthermore each right-hand fragment is then responsible for 'replacing' itself with a new fragment and handling results sent back to it. I'm using setTargetFragment, getTargetFragment, and calling onto the target fragment's onActivityResult method to pass results back.
For me the approach I've taken is no different from when my app runs on a phone with a single pane whereby the initial option's activity only knows about the activies it fires off and subsequently these new ones fire off further activies which they know about.
It should be mentioned that my activity in my dual pane app doesn't really do much apart from loading the left pane fragment and I can't quite see the need for a single activity to ever have to manage hundreds of fragments.