android.app.Fragment android.app.FragmentManager.findFragmentByTag(java.lang.String)'
on a null object reference
I had found numerous posts with this issue here on SO, but none of them helped me.
It happens relatively rarely (1/100). The place where I call the getFragmentManager() is on the main thread, when a button is clicked :
My flow is simple , first I have fragment A, then add on it several fragments until I get to fragment X.
Once a button is clicked in a custom view class that is instantiated and held in the fragment X, I call :
fragment A.getInstance().showLoadBar();
in fragment A the showLoadBar calls a fragment just for displaying loading, and it uses the getFragmentManager() there.
Most of the time it works great, and I am not able to recreate this crash. But I can see that this crash does happen sometimes.
Related
I read many threads to this topic but nothing could help me.
I can´t post my code, but i try to explain my situation:
I´m getting an IllegalStateException: can not perform this action after onSaveInstanceState
I have an activity Main (with an DrawerLayout / navigation drawer). In this activity is always 1 fragment. At first there is the fragment WelcomePage.
When the user clicks the fragment gets replaced with new fragment (I call it: FootballClubs). This fragment contains a ViewPager, so it consists of multi pages; each page is a fragment FootballClub. (So the fragment FootballClubs consists of multi pages of one FootballClub fragment). In each FootballClub fragment is a button where the user can click; on click opens a new fragment (I call it NewFragment).
At first, everything is ok and no exception is thrown. I can click the button as often as possible, each time the new fragment is shown correctly without an exception, and onBackPressed I get back to FootballClubs with the ViewPager. But when I turn my smartphone, screen orientation has changed at least one time, I get following exception when I click on the button to open the new fragment (whatever fragment I want to show):
IllegalStateException: can not perform this action after onSaveInstanceState
I replace the fragments WelcomePage/FootballClubs/NewFragment with this code:
protected void replaceSubfragment(int containerID, Fragment newFragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(containerID, newFragment);
transaction.commit();
}
The exception is thrown in transaction.commit();
containerID is always the same (FrameLayout in activity layout)
Some additional information:
If I replace
transaction.commit();
with
transaction.commitAllowingStateLoss();
I get this Exception: IllegalStateException: Activity has been destroyed (although I click/do exactly the same)
All fragments gets replaced in my FragmentActivity class Main; When you click on the button, the Subclass of SectionPage gets noticed and calls a listener from FragmentActivity, so that the FragmentActivity can replace the fragments (BaseActivity gives the listener over to FootballClubs fragment, and this gives it over to the Subclass of SectionPage)
Due to the fact that I only replace fragments in my FragmentActivity I don´t use getChildFragmentManager(); (As far as I can remember this didn´t work too)
I know that the activity gets destroyed and recreated by the system when orientation changed, but I can´t do something with this information.. the whole app works perfectly except this button
If I want to show a dialog instead of NewFragment on button click I get an IllegalStateException too
In my FragmentActivity, I have a variable activeModule; here I can check activeModule.isAdded(); When the exception is thrown, isAdded() return false.. So I can check if the error will be thrown or not
I hope I could describe my problem. The screen orientation ruins everything, don´t know why..
If you need further information ask me.. if you need some code pieces maybe I can post something.
Thanks!
I am hitting a very strange problem in Android and I can't figure out why it's happening or how to code around it. I truly believe this to be an Android bug.
I have a MainActivity which contains a FrameLayout named main_container (its height and width are both match_parent as each fragment should be the only fragment "showing" to the user). From MainActivity, I add Fragment A like so:
mFragmentManager.beginTransaction()
.replace(R.id.main_container, frag, fragTag)
.commit();
From there, Fragment A, upon a user's click of a view, will add Fragment B like so ("frag" and "fragTag" are different values than the above code snippet):
mFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.slide_in_right, 0, 0, R.anim.slide_out_right)
.add(R.id.main_container, frag, fragTag)
.addToBackStack(null)
.commit();
And from here, Fragment B will add Fragment C like so (again, "frag" and "fragTag" are different values than the previous two snippets):
mFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.slide_in_right, 0, 0, R.anim.slide_out_right)
.add(R.id.main_container, frag, fragTag)
.addToBackStack(null)
.commit();
So at this point, in the backstack, we should have Fragment A -> Fragment B -> Fragment C.
Fragment C invokes the MediaPicker upon the user's click of a view. Doing so calls all fragments' onPause methods and the app is put in the background. Now when the user selects an image, the application is resumed but here's where the bug happens... it resumes in this order, as proven with breakpoints in each fragments' onResume method:
Fragment A -> Fragment C -> Fragment B
This causes all sorts of issues because each of my fragments registers itself as a listener in the MainActivity to handle back button clicks. This logic relies on that ordering being correct. For some reason, it's still showing Fragment C on top, but onResume was definitely called out of order.
Perhaps even worse though... rather than clicking for MediaPicker, you can simply rotate the phone to cause a config change. This exhibits the same behavior of reordering to A -> C -> B but in this case it DOES actually show the wrong fragment on top. It SHOWS Fragment B on top.
Is it a design point that you can't rely on Android to resume fragments in the same order you added them to the backstack and I'm supposed to code around it? Or am I doing something wrong? Or is this really an Android bug? I am by far not a newbie to Android development, but this one has me stumped.
EDIT:
I've pinpointed what is going on and apparently it's by design. It seems pretty crazy to me and I disagree with the logic behind it. I may be able to fix this with reflection, but I don't like doing that. Anyways, on to the problem.
The problem is with the way FragmentManagerImpl keeps track of active fragments. It has an ArrayList to keep track of active fragments and when everything is paused (such as in my case where I'm starting an intent to get a photo from media gallery, thus it's leaving my app), upon resuming back into my app, it moves the fragments back to active in the same order they're in that ArrayList. Sounds great, eh?
Well here's my problem. When things are taken OUT of that ArrayList, they don't remove() the item, they just set it to null and then have logic to reuse that empty "slot" (line 1168 in the github link) when the next fragment comes along. In my case, the transient fragment that leaves a hole in the ArrayList is a DialogFragment. Putting it back into terms of my original report, Fragment A shows a DialogFragment... clicking a certain button in that DialogFragment brings up Fragment B. Clicking another view in Fragment B brings up Fragment C. But here's what happens to the ArrayList FragmentManagerImpl keeps track of after clicking the button in the DialogFragment:
{ FragA, null (used to be DialogFragment), FragB }
So apparently DialogFragment was moved out of active state after FragB was moved to active, thus leaving a hole. So now we click the view in FragB to bring up FragC and the ArrayList looks like so:
{ FragA, FragC (reused DialogFragment's slot), FragB }
We go off to the media picker, come back, and voila the fragments are resumed out of order with respect to how I instantiated them in the first place. This makes no sense to me and if you don't step into OS code with breakpoints, you never figure out why Android is not behaving the way you told it to. Seems like it would have been easier to just do an ArrayList.remove() of the fragment you removed, thus leaving no holes.
Like I said, I can probably get around this with reflection... but I'm leery of that because there is also this mIndex variable in all Fragments that corresponds to the index of it's slot in that ArrayList (mActive). So I'd have to be sure to keep those in sync... and now I have a dependency on knowing how the OS code works. :(
This is a known issue. Google "android fragment reordering" and you will get a whole page of links on the subject including some solutions.
Alright. My first question here. And I already found some solution, but honestly do not really get the stuff that happens in the background. So perhaps there’s someone who could clear up this stuff a little bit. After days of debugging I’m just glad that it works... and hope I did not make some serious error. So let’s have a look.
I got some Main-Activity. Just a FragmentActivity extending JFeinstein’s SlidingFragmentActivity. Further I decided to go the fragment-way and just put any content (list-fragment, article-fragment, …) as a fragment into a container (to right of the sliding-menu); my main-container. So far, so good.
One essential fragment is my article-fragment. A ViewPager (with a FragmentStatePagerAdapter) - containing some pages with text and perhaps another list-fragment. Still no problem so far, until I decide to rotate the device. And to be more precise, rotating the device works too as long as I do not decide to update my article-fragment.
I understood (correct me if I am wrong) that Android handles the fragments state on its own when rotating the device. And it seems to be everything fine just until I want to reload/update its content.
Ok let’s dig deeper into that.
On first start I got some empty main-container. Then I am loading my article-fragment for the first time. Just getting the SupportFragmentAdapter, creating my ArticleFragment and replace the main-container with the newly created fragment - tagged. No rocket-science - just a simple transaction:
ViewPagerFragment pagerFragment = (ViewPagerFragment)
getSupportFragmentManager().findFragmentByTag(TAG_FRAGMENT_ARTICLE);
if(pagerFragment != null){
if(pagerFragment.isResumed()){
pagerFragment.triggerReload();
}
} else {
pagerFragment = new ViewPagerFragment();
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.replace(R.id.id_main_root_frame, pagerFragment, TAG_FRAGMENT_ARTICLE);
t.commitAllowingStateLoss();
}
To avoid creating a fragment each time I reload my content, I’m trying to fetch the fragment before the transaction and - if it is found and resumed - trigger some reload on the existing fragment.
Now I rotate my device in this state. To avoid messing with the fragment state I left onSaveInstanceState() inside the fragment untouched. So I guess the fragment is just destroyed and recreated. And everything still works so far. But I think this part has something of a black box.
After that - normal startup, creating fragment and put into main-container, rotating device - I trigger some update. But instead of finding the old (recreated) fragment by tag, nothings found and a new fragment is created and inserted. At least tried to be inserted, because this is where I got the following exception:
java.lang.IllegalStateException: Activity has been destroyed
To be precise, I get the above exception when finish my transaction with a commitAllowingStateLoss(). When I just commit() the transaction I get the following exception:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
So that’s where the error comes up. And after ages of debugging and searching I found some hint on this question/answer to get the SupportFragmentManager on a WeakReference of my MainActivity. And what should I say. Since I implemented that, it works. I had to change my update-process a bit, but it works. But leaves some questions ...
The behaviour seems to be similiar. First creation works perfect. Reload just the same - fragment is found by tag. After rotation, article is still shown. And when I reload the fragment with that state it is not found by tag so a new one is created, but the commit()-request does not throw an exception. A look inside the debugger shows that the WeakReference is some other instance (other id), than the one(this) all of this takes place in. And thats where I lose the plot. ..
If some of you could give me some hints, would be great!
Thanks in advance!
try this:
commitAllowingStateLoss(); instead commit();
In my application, I use an Activity which holds one Fragment with FragmentTabHost and hence all its tabs are nested Fragments.
Inside an Activity which holds a Fragment with its nested Fragment, we may get a reference to attached one using onAttachedFragment().
But how to get a reference to nested Fragment from FragmentTabHost?
Well, exploring the source code of FragmentTabHost I've found that when it adds a fragment tab, it assignes a tag of TabSpec to nested Fragment.
So to get the reference to this Fragment we should call
getChildFragmentManager().findFragmentByTag(tabSpecTag)
I was trying this for a while, but I was getting null returned from the FragmentManager because I was trying to access the manager in onCreateView() immediately after adding.
Here is a good explanation on what happened
It's also important to note that Fragment tabs that have not yet been selected don't exist yet in the FragmentManager, and so will return null as well. I got around this by calling mTabHost.setCurrentTab(index) before trying get to the Fragment with the FragmentManager. It's not very clean, but it works.
Above solutions are also working but I have one more easy solution,
#Override
public void onTabChanged(final String tabId) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mFragment = getChildFragmentManager().findFragmentByTag("Tagname");
}
},1000);
}
Here you have to implement FragmentTabHost.onTabChangeListener
We have kept a second delay in fetching fragment from the childFragmentManager.
Note : You need to cast mFragment which fragment you have used.
I found a solution that I like a little better because it doesn't involving executing code with a delay (which is always iffy given android hardware fragmentation and different processor speeds).
In your onTabChanged() method, before you try to find the fragment, call executePendingTransactions() on the fragment manager associated with your tabHost. It seems there are some places in the FragmentTabHost source code where they should be calling executePendingTransactions() but fail to do so.
This works every time the tab changes with one exception... the first tab that is selected still comes back null... In my specific case, I was able to handle this exception differently anyway, by putting some code in onResume.
Hope this helps.
I am facing memory leak because some fragments are not removed. I have Activity that populates view with Fragment F1. This fragment has ViewPager and FragmentStatePagerAdapter associated with it. Adapter is feeling pager with another Fragment F2. Problem starts when in Activity I remove Fragment F1. Existing Fragments F2 are not removed.
any suggestions?
Not sure if it is the same problem, but some time ago I had a problem with viewpager where the fragment lifecycle methods were never called and the problem was with the fragment manager.
So, when you create the adapter try passing childFragmentManager instead of supportFragmentManager and see if it fixes your issue.
I have the exact same problem. Try to move your fragment F1 back into an activity. That might helps you determine if the problem is to have a fragment in a fragment. Android doesn't really like that. If I find something on my side, I will let you know
I was facing the same issue here. After researching for a long time about how to correct it in a "right" way I couldn't get it working. So I was forced to explicit release all F2s fragments before removing the F1 parent fragment (with a fragment transaction) from my activity.
// Remove all content from the FragmentStatePagerAdapter instance.
myAdapter.Content.Clear (); // Content here may be an ArrayList in Java or a List in C#.
myAdapter.NotifyDataSetChanged ();
// Reset current empty adapter inside the ViewPager (this will make all existing F2 fragments to be released).
myViewPager.Adapter = myAdapter;
// Do the transaction removing the parent F1 fragment from the main Activity.
FragmentManager.BeginTransaction ()...
By following these steps, all F2 fragments started being released form memory and they are also having their OnPause, OnDestroyView ... OnDestroy callbacks being invoked as well.