There are 2 fragments there. One of them contains an EditText (fragmentA).
If I update the EditText by calling EditText.setText() from another Fragment it doesn't display the newly set text although it has been set as I've set a breakpoint and saw it. It still shows the old text.
The following code is executed in the fragment without EditText if user clicks a button:
fragmentA.getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
editTextInFragmentA.setText(text, TextView.BufferType.EDITABLE);
}
}
// Close the fragment
getActivity().getSupportFragmentManager().popBackStack();
What's wrong with this approach? May it be caused by the fact that fragmentA is hidden by another fragment when EditText.setText is called?
I can realize from the popBackStack that your probably using replace when placing fragments into their containers. When a replace transaction is made, the old fragment gets it's state saved and all of the UI components (EditText, TextView, Button) get their state saved as well. Once the state is saved, the UI components gets destroyed such that trying to reference them after the old fragment has been removed from view will not work (usually causing NPEs if you try to set or change something in them but I'm not sure what's happening in your case). Upon going back to the old fragment using popBackStack, the UI is re-inflated within the fragment onCreateView, hence you'll have newly created UI component. In case there's an available saved state, it will be used to revert the UI back to when it was before it got replaced with another fragment. Since the text in the EditText got saved with the saved state, it was reverted back when you went back to the old fragment, hence setting it from the new fragment doesn't help. This technique is for efficiently using memory hence preventing the app from causing OutOfMemory exceptions.
To correct this, the old fragment should contain a callback which the new fragment could call to update the EditText value when UI is re-created. The callback would be passed from the old fragment to the new one, and then the new fragment would call this callback and pass it the new string. This new string should be stored first using a global variable inside the old fragment. When the old fragment is re-creating the UI in it's onCreateView, check if the global string variable is not null and not empty and hence update the EditText using setText().
Related
In my application I create a fragment with the keyword new and set it by FragmentTransaction.
Upon rotation a stumbled upon a NullPointerException in the method onActivityCreated() indicating a missing injection, that I do after the call to new. I suspected the fragment was not created by my code und proved this by logging the hashCode(). It looks like a fragment is created automatically by the system upon rotation.
Where does it come from?
Is it created by the fragment manager?
How am I supposed to use it correctly?
How can I access it, to set the missing value?
For now I ignore it by testing for the null value, in which case onActivityCreated() does nothing. Instead use the fragment I create with new. However, this does not feel very satisfying, to throw away an object, that was already created.
Where does it come from? Is it created by the fragment manager?
On Activity recreation, Android will restore the fragments which already exist in activity's fragments manager
How am I supposed to use it correctly?
public void onCreate(Bundle savedInstanceState){
if(savedInstanceState == null){
//activity is created for first time
//commit the fragment
}else{
//Activity is recreated(by means of rotation or something else)
//Dont commit the fragment, fragmet will be restored by the system
}
}
How can I access it, to set the missing value?
Normally, you have to handle this inside the fragment using onSaveInstanceState method. You can get the fragment instance by using, getSupportFragmentManager.findFragmentById(R.id.container) or getSupportFragmentManager.findFragmentByTag(tagName)
I'm well aware of what an IllegalStateException is and why it happens when you are trying to commit FragmentTransactions after instance state has been saved. I've read through most of the popular Stackoverflow questions on the subject as well as "The Blog Post."
I have a particular scenario in which I've created a Fragment to display a custom progress spinner while the app is waiting for some search results to come in. So my search Activity is running in singleTop mode and relies on onNewIntent() to perform the search and display the spinner. This all works fine, but fails when run-time changes come in (like orientation changes). I'll get an IllegalStateException if I try removing the progress spinner Fragment after an orientation change, or when adding the spinner after a voice search. My setup looks like this:
#Override
protected void onNewIntent(Intent intent) {
if(intent.getAction().equals(Intent.ACTION_SEARCH)) {
performSearch(intent.getStringExtra(SearchManager.QUERY));
}
}
The performSearch() method sends the request to my server and then adds the progress spinner Fragment like this...
private void showProgressSpinner() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(R.anim.fragment_fade_in, R.anim.fragment_fade_out);
ProgressFragment spinner = ProgressFragment.newInstance();
transaction.add(R.id.searchContainer, spinner, "spinner");
transaction.commit();
}
Then when the search results come in through an asynchronous callback, I remove the progress spinner Fragment like this...
private void dismissProgressSpinner() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(R.anim.fragment_fade_in, R.anim.fragment_fade_out);
ProgressFragment spinner = (ProgressFragment) fragmentManager.findFragmentByTag("spinner");
if(spinner != null) {
transaction.remove(spinner);
}
transaction.commit();
}
If an orientation change comes in while the progress spinner Fragment is being displayed, I get the IllegalStateException when the search results return and I try to remove the spinner Fragment. According to Alex Lockwood, putting the FragmentTransaction in onPostResume() can help, since you are guaranteed to have the state restored by then. This indeed works, but I need to remove the Fragment in my asynchronous callback from the search results, not when the Activity resumes.
So my question is, how can I commit this Fragment transaction after a state change, but within my async callback? I've tried using commitAllowingStateLoss() but I still get the exception because my spinner Fragment is still referencing the old destroyed Activity. What's the best way to handle this?
I've tried using commitAllowingStateLoss() but I still get the exception because my spinner Fragment is still referencing the old destroyed Activity.
your fragment was recreated after config change, ie. after user have rotated screen - your spinner fragment will be destroyed and recreated, and after onAttach it will reference new activity instance. Also all of this process is done by android on UI thread in single message, so there is no chance that your async operation callback (which should execute also on UI thread) gets executed in the middle.
I assume here you are not creating some local reference to activity inside ProgressFragment.
You could write additional logic that would make sure your commit is called in valid moment. ie. in your activity onStart set some static boolean allowCommit to true, and in onPause set it to false. Also add some static variable, searchWasFinished, and in your async callback check if allowCommit is true if so then immediately remove spinner, if not then only set searchWasFinished to true. Inside your Activity.onStart check if searchWasFinished==true and if so then remove fragment with commit. This is just an idea, probably more logic would have to be put in it.
I have 2 fragments which are called from the action bar of an activity. Both are gridviews, the first one displays applications with a dedicated adapter, and the second one displays a file list with another adapter. My problem is that when I launch a file then when I back to my activity I switch from one fragment to another, when I come back to the previous one, its content disappears. And when I rotate tablet I have the some problem, because my Fragment restart so for this I think that removing fragment give the possibility to create a new Fragment up to date. How can I save and reload data in my fragment.
How can I manage to update the content of the first fragment while coming back from the second one ? And how to remove fragment after the rotation in order to recreate the Action with new Fragment? I asked this questions but I don't have any responses. the code is given below
If your data is just strings or integers, you can make use of shared preferences to store and retrieve data.
Solution to your first problem -how to save fragment state
Use setRetainInstance(true) in you fragments onCreate() *it prevents your fragment from destroying and hence recreating.
Add your fragment to back stack
declare your adapter globally in fragment and resuse it when you get back.
when, you get back to fragment its onCreateView() method will be called directly. hence initialize your adapter in onCreate() method and use it in onCreateView().
Solution to your second problem -how to update fragment content
For this you can use interface. create interface in your second fragment and implement it in your first fragment. prefer this doc for this,
http://www.vogella.com/tutorials/AndroidFragments/article.html#fragments_activitycommunication
I have a detected a bug in my app today : When a user clic on an item in my listview, it launch a new fragment with details about this item. But when the user is in this fragment, if he change orientation of the device, the onCreate method of my first class (which fetch my listview) is called, and the app crashed.
I would like to know if it's possible to get the name of the previous fragment, in order to add a test like the following in my onCreate method :
if (!fragmentName.equals("common")){
Log.d("INFO", "Do nothing in this case, because user was in detail fragment before !"),
} else {
super.onCreate(savedInstanceState);
refreshList(true);
}
I don't think it is possible to get the name of the previous fragment as its creating a new instance of your Fragment object.
If I am right in what you are trying to do you need to somehow store the object for which you are displaying data so that when the Fragment is recreated you can then retrieve the required object. One way is you keep the ID or a unique property of the object in SharedPreferences and then reacquire its data when it reloads from a singleton somewhere.
Basically you may need to change the way you retrieve your data or pass it from the original opening Fragment so that you can retrieve it again if the second Fragment is destroyed.
Here's the scenario that causes problems:
I start an Acitivity with a ViewGroup that'll hold the presented fragments.
I load Fragment A into this ViewGroup by calling .replace() in the transaction that I save onto the backstack.
I load Fragment B into the Viewgroup, replacing Fragment A. Again, I save the transaction.
I rotate the device twice.
On the second rotation, Fragment A (which is not visible at the moment) will throw a NullPointer exception.
This exception is thrown by Fragment A, because I'm saving some values of Views (EditTexts e.g.) in its onSaveInstanceState() method. It seems, that on the second rotation, the system doesn't instantiate these Views, so when I'm asking their values, I get a NullPointer exception. Can this be avoided somehow? Or is using .replace operations in a fragment transcation saved onto the backstack unadvised?
I've had this but can't quite recollect the specifics of what I did to fix but generally speaking (and apologies for the brain dump) I do the following:
Call setRetainInstance(true) in onCreate to avoid fragment recreation
Save the state of edit controls etc. in onSaveInstanceState to be used if activity is killed and you get restored with a non-null bundle (you shouldn't get a non-null bundle on an orientation change with 1.)
Maintain edit control values in member variables (as the fragment is not going to be recreated) ensuring they get updated in an onDestroyView from the edit controls and then use them to restore the edit control values in onCreateView
Have a flag which I set to true in onCreateView and false in onDestroyView and ensure I don't touch UI controls when the view is not around.
BTW Using replace while adding the transaction to the back stack is perfectly OK.
Hope there's something in there that helps. Peter.