I have an Activity with a FrameLayout and need to show different fragments based on user input.
The code I use for showing a fragment is this:
private void showFragment(Fragment fragment, Bundle args, boolean addToBackStack) {
if (args != null) {
fragment.setArguments(args);
}
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.setCustomAnimations(R.anim.activity_open_translate, R.anim.activity_close_scale);
fragmentTransaction.replace(R.id.main_frame, fragment);
if (addToBackStack) {
fragmentTransaction.addToBackStack(fragment.getClass().getName());
}
fragmentTransaction.commit();
}
This is called as :
if (contactPickFragment == null) {
contactPickFragment = new ContactPickFragment();
}
showFragment(contactPickFragment, args, true);
All this works fine. Now if the user goes into one fragment presses back and returns back to the same fragment, all my views inside stay the same. For example, I have an EditText inside the fragment and the user edits something inside. If the user comes back to the fragment, the same text persists. I do not want this to happen. How do I reset everything in the view?
I have added code within the Fragment's onCreateView() to clear the text, and from debugging I see that this is being called, but the text never gets cleared. What am I doing wrong here?
If you don't want the data from the previous instance to appear, simply create a new instance of ContactPickFragment each time you show it.
Clearing data in onCreateView() has no effect because view state is restored AFTER onCreateView(). Your Fragment has no view before onCreateView() and so Android cannot possibly apply the previous state any earlier. Values set on the views during onCreateView() will be overwritten by their previous values.
As a general answer, there is no way to "refresh" the view of a Fragment, other than replacing the fragment with another instance of itself (possibly initialized with the parameters that you want to refresh/update).
You can reuse your fragments and refresh the state of your views. You just can't do it from onCreateView as #antonyt correctly points out.
Instead, override onViewStateRestored and set up the state of your views the way you'd like from there.
Something like:
#Override
public void onViewStateRestored(Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
View view = getView();
// Code to call view.findViewById to grab the views you want
// and set them to a specific state goes here
}
There are advantages to reusing fragments. Not the least of which is that if you have a memory leak with your fragment (which is easier than you may think to accomplish,) you will exacerbate the problem by creating myriads of them.
Related
I am working on an application and there is one specific thing that is bothering me. Let's just say I have one activity and 2 fragments.FragmentA and FragmentB and FragmentA gets attached when activity starts.
I want to save the fragment data and fragment state when orientation changes occur.I have successfully saved fragment data using OnSavedInstanceState method. Now I want to save fragment state in the activity so that if orientation change occurs I want to be on the fragment I was (in my case either FragmentA or FragmentB depends on which was showing before config changes occur).
This is how I am saving the fragment state in the Activity:
#Override
protected void onSaveInstanceState(Bundle outState) {
// Save the values you need into "outState"
super.onSaveInstanceState(outState);
outState.putLong(SS_DATE, userDate.getTime());
android.support.v4.app.FragmentManager manager = getSupportFragmentManager();
Fragment currentFragment = this.getSupportFragmentManager().findFragmentById(R.id.content_container);
manager.putFragment(outState, "currentFragment", currentFragment);
}
And this is how I am retrieving on which fragment I was when the orientation change occurred:
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
FragmentManager manager = getSupportFragmentManager();
#SuppressLint("CommitTransaction")
FragmentTransaction transaction = manager.beginTransaction();
if (savedInstanceState != null) {
Fragment MyFragment = (Fragment) manager.getFragment(savedInstanceState, "currentFragment");
if (MyFragment instanceof FragListStudentsAttendance) {
Log.v("onRestore", FragListStudentsAttendance.TAG);
}else if (MyFragment instanceof FragGetClassesForAttendance){
Log.v("onRestore", FragGetClassesForAttendance.TAG);
if(MyFragment!=null) {
mFragGetClassesForAttendance = (FragGetClassesForAttendance) MyFragment;
}else{
mFragGetClassesForAttendance = new FragGetClassesForAttendance();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// mFragGetClassesForAttendanceNew.setRetainInstance(true);
// transaction.replace(R.id.content_ssadmin_container, mFragGetClassesForAttendanceNew, "FragGetClassesForAttendance").addToBackStack(null);
transaction.add(R.id.content_ssadmin_container, mFragGetClassesForAttendance, FragGetClassesForAttendance.TAG);
//transaction.replace(R.id.newEnrollmentMainContainer, mFragNewEnrollmentResults).addToBackStack("FragNewEnrollments");
transaction.commit();
mFragGetClassesForAttendance.setDate(userDate);
}
}
}
}
Now
Scenario 1:
If I am on fragment A and I rotate the device every thing works fine as it should. Like fragment have web services which loads the data into listview so I check if data exist then there is no need to run the web service and that working for now
Scenario 2:
If I am on fragment B and orientation change occurs everything works fine as it is supposed to be on fragment B. Now When I press back button Fragment A gets called again and all the data also comes from service. I think this shouldn't happen because it was supposed to be in BackStack and it's data was saved. So what Should I do now here?
Scenario 3: On FragmentB I have noticed that when I rotates the device the saveInstanceState function of FragmentA also gets called. Why it is so? where as I was replacing the FragmentB with FragmentA ?
Some Confusions:
Let me talk about some of the confusions also , maybe someone clear it to me although I have searched and read a lot about fragment and activity life cycle,
Actually I want to save the data per activity and fragment on device rotation. I know how to do it with activity(how to save states) so I also know how to do it in the fragment (save state of fragment views) now I am confused how to tell activity which fragment was showing and which to go after config changes(rotation) ? also what happens to FragmentA if I am on FragmentB Does its get attach and detach again and again in background?
I got your problems and confusions. I think the life cycle of fragment is confusing you. and indeed it will confuse you.
You need to learn different situations.
1. Fragment Life cycle when it is in foreground (attaching and detaching with activity) . Please keenly observe all the methods that will call i.e OnSaveInstance,onCreateView,OnDestroyView,onDestroy
2. Fragment life cycle when it is in background (observe the methods stated above)
3. Fragment life cycle when it is added to backstack (and not in foreground)
I am quite sure you are confused with the point number 3. As when the fragment is added to backstack it never gets destroy. So rotating device twice will set the ffragment data to null. I think you are restoring data on ActivityCreated or on onViewCreated ,
Ill suggest you to restore the fragment data in the oncreate. this will work for you when your fragment is coming back to foreground from the backstack .
Example
private List<String> mCountries;</pre>
#Override
public void onCreate(Bundle savedInstanceState)
{
if (savedInstanceState != null)
{
// Populate countries from bundle
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_countries, container, false);
if (mCountries == null)
{
// Populate countries by calling AsyncTask
}
return view;
}
public void onSaveInstanceState(Bundle outState)
{
// Save countries into bundle
}
Hope this will clear your confusions.
I am creating a Fragment Activity with tabs. Now whenever i select a tab, corresponding fragment gets recreated and its
onCreateView()
method is called.
But what i want is to reuse the view of the fragment so that whenever a tab is selected system does not call
onCreateView()
of fragment instead it shows the previous view of the fragment if it exists.
Please reply soon.
You may want to show and hide the fragments instead of adding and removing them when selecting a tab.
For example:
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if (frag.isAdded()) {
transaction.show(R.id.layout, frag);
} else {
transaction.add(R.id.layout, frag);
}
transaction.commit();
setOffScreenPageLimit() method can be used to set how many fragments you want to keep alive even if they are not visible to the user.
Try this, it works for screen rotation and should work for your situation as well:
1) When you add fragment for the first time, create it with name parameter:
getSupportFragmentManager()
.beginTransaction()
.replace(<containerID>, <fragment>, YOUR_FRAGMENT_NAME_CONST)
.commit();
2) When you need to find it:
YourFragmentType fragment;
fragment = (YourFragmentType) getSupportFragmentManager()
.findFragmentByTag(YOUR_FRAGMENT_NAME_CONST);
if (fragment != null){
//TODO set data to the existing fragment
}
else{
//TODO create and initialize your fragment
}
Edit:
You should differentiate Fragment object creation and onCreateView() being called.
It's right that you should avoid unnecessary object creation and so reuse fragments.On the other hand (as for my experience) it's better to adhere Android's onCreateView() politics to guarantee consistent user experience.
If you really want to save CPU time and avoid re-inflating complicated view (and settle all issues yourselves) - you may just check it is null like this:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// View
if (mView == null) {
mView = inflater.inflate(R.layout.your_fragment, container, false);
...
}
...
}
First, some background information. I have an Activity that hosts several Fragments; these are hidden and shown such that only one Fragment is visible at a time. Each Fragment hosts several custom Views that I call IncrementCounters. Those views display a number and increment that number by 1 when tapped.
Each of the Fragments has setRetainInstance(true) called on it when it is created in my Activity. When the Activity is created, I check to see if the Fragments exist; if they do, I store a reference to them; if not, I create a new instance, like this:
autonFragment = (AutonomousScoutingFragment) getFragmentManager()
.findFragmentByTag("auton");
teleopFragment = (TeleoperatedScoutingFragment) getFragmentManager()
.findFragmentByTag("teleop");
postMatchFragment = (PostMatchScoutingFragment) getFragmentManager()
.findFragmentByTag("post_match");
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
if (autonFragment == null) {
Log.d("onCreate", "autonFragment is null!");
autonFragment = new AutonomousScoutingFragment();
autonFragment.setRetainInstance(true);
ft.add(R.id.scouting_fragment_container, autonFragment, "auton")
.hide(autonFragment);
}
if (teleopFragment == null) {
Log.d("onCreate", "teleopFragment is null!");
teleopFragment = new TeleoperatedScoutingFragment();
teleopFragment.setRetainInstance(true);
ft.add(R.id.scouting_fragment_container, teleopFragment, "teleop")
.hide(teleopFragment);
}
if (postMatchFragment == null) {
Log.d("onCreate", "postMatchFragment is null!");
postMatchFragment = new PostMatchScoutingFragment();
postMatchFragment.setRetainInstance(true);
ft.add(R.id.scouting_fragment_container, postMatchFragment,
"post_match").hide(postMatchFragment);
}
ft.commit();
One problem I have is that after every orientation change, it seems as though the Fragments aren't actually being retained, as ever time I see debug prints stating that they are all null. This may be realted to my bigger problem; I'm not sure.
I am trying to figure out how to maintain the value of the number stored in each IncrementCounter across configuration changes, specifically rotation. I have overridden onSaveInstanceState() and onRestoreInstanceState() in IncrementCounter. When I rotate my device, I see that onSaveInstanceState() is called on all of the IncrementCounters I have in my Fragments. However, the corresponding onRestoreInstanceState() is never called, and my IncrementCounters do not have their states restored. What is the proper way to handle something like this? I've been banging my head against my desk for hours about this problem.
Since the Fragments have the setRetainInstance(true), they will not be destroyed on a rotation. The Activity will be destroyed, and the Fragments will be detached until a new Activity is created. However, the Fragments will go through the onCreateView() method again, so you would need to restore their IncrementCounters.
Also, you can save state in onSaveInstanceState(), and then restore it in onCreate(), onCreateView(), and several other methods that are all passed that same bundle as a parameter.
I have this activity where I run an asynctask that gathers a series of data. This data can be represented in several ways, for the sake of simplicity let's say there's two ways. I want the user to be able to switch between these two representations (listview and graphical view).
So I created a FragmentActivity with my ListFragment and my GraphicFragment.
How do you switch between these two so that user can go back and forth? I tried using replace but then the previous fragment gets destroyed which is not of my interest since its reference has the data passed by my Activity. (i.e. I don't want to recreate the fragment each times the user switches between views).
I also tried hiding and showing but I can't get it to work, one of the fragments is not showing.
Also, when I rotate I guess the fragments get destroyed because I get a nullpointerexception when trying to access them.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
pBar = (ProgressBar) findViewById(R.id.analyzingProgress);
// However, if we're being restored from a previous state, then don't
// create the fragment again (please)
if (savedInstanceState == null) {
firstFragment = new DirListFragment();
secondFragment = new GraphViewFragment();
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, secondFragment).commit();
fragments= new ArrayList<IFragment>();
fragments.add(firstFragment);
fragments.add(secondFragment);
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if(item.getItemId() == R.id.listView){
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
/*transaction.hide(secondFragment);
transaction.show(firstFragment);*/
transaction.replace(R.id.fragment_container, firstFragment);
transaction.addToBackStack(null);
transaction.commit();
}
else if(item.getItemId() == R.id.graph){
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
/*transaction.hide(firstFragment);
transaction.show(secondFragment);*/
transaction.replace(R.id.fragment_container, secondFragment);
transaction.addToBackStack(null);
transaction.commit();
}
return true;
}
The problem comes when I try to access the fragment fields to update their data:
for(IMyFragment frag: fragments){
frag.updateData(data);
}
I get a NullPointerException after rotating the screen.
Also when switching fragmenents, their data is not updated (i.e. I start with list fragment, it's showing the proper data, then switch to graphic, no data is shown, switch back to list, no data again.)
If the fields are null when using setRetainInstance(true);, then you are recreating them (the fragments) in your activity. Make sure you are setting something in onSaveInstanceState() in your activity so in onCreate(), savedInstanceState will not be null, it can even be an empty bundle or useless variable, ex:
#Override
public void onSaveInstanceState(Bundle outstate){
super.onSaveInstanceState(outstate);
outstate.putBoolean("stateChanged", true);
}
Just make sure you call super before adding your arguments.
On another note, I personally have never stored my fragments in an array. I use a FrameLayout as a fragment container in my activities, just like you, and if I need to update something I just call: getSupportFragmentManager().findFragmentById(R.id.fragment_container).setWhatINeed(); This way I always get the visible fragment.
Let me know how the update goes and I'll update this post with any information I can.
Can I use savedInstanceState() to save the state when removing a fragment, then restore the state when I pop the fragment off the back stack? When I restore the fragment from the back stack, savedInstanceState bundle is always null.
Right now, the app flow is: fragment created -> fragment removed (added to back stack) -> fragment restored from back stack (savedInstanceState bundle is null).
Here is the relevant code:
public void onActivityCreated(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle bundle = getArguments();
Long playlistId = bundle.getLong(Constants.PLAYLIST_ID);
int playlistItemId = bundle.getInt(Constants.PLAYLISTITEM_ID);
if (savedInstanceState == null) {
selectedVideoNumber = playlistItemId;
} else {
selectedVideoNumber = savedInstanceState.getInt("SELECTED_VIDEO");
}
}
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(Constants.SELECTED_VIDEO, selectedVideoNumber);
}
I think the problem is that onSavedInstanceState() is never called when being removed and being added to back stack. If I cant use onsavedInstanceState(), is there another way to fix this?
onSaveInstanceState is (unfortunately) not called in normal back-stack re-creation of a fragment. Check out http://developer.android.com/guide/components/fragments.html#Creating and the answer on How can I maintain fragment state when added to the back stack?
I like to store the View I return in onCreateView as a global variable and then when I return I simply check this:
if(mBaseView != null) {
// Remove the view from the parent
((ViewGroup)mBaseView.getParent()).removeView(mBaseView);
// Return it
return mBaseView;
}
The problem is that the fragment needs to have an Id or Tag associated with it in order for the FragmentManager to keep track of it.
There are at least 3 ways to do this:
In xml layout declare an Id for your fragment:
android:id=#+id/<Id>
If your fragments container View has an Id, use FragmentTransaction:
FragmentTransaction add (int containerViewId, Fragment fragment)
If your fragment is not associated with any View (e.g. headless fragment), give it a Tag:
FragmentTransaction add (Fragment fragment, String tag)
Also, see this SO answer.
FWIW, I hit this as well, but in my case onSaveInstanceState was called properly and I pushed in my state data when a new activity fragment was brought up on the smartphone. Same as you, the onActivityCreated was called w/ savedInstanceState always null. IMHO, I think it's a bug.
I worked around it by creating a static MyApplication state and putting the data there for the equivalent of "global variables"...