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();
Related
I have an issue with showing DialogFragment inside Fragment. It does not matter what I try, I cannot get it to work everytime.
My layout consist of activity with navigation drawer and inside activity I am showing fragments. On those fragments I must show DialogFragment but for that, I must get context/application context/activity/whatever (depends on current year and weather) to show it.
In most cases it works ok but sometimes, even if fragment is constantly showed to user (no configuration changes or anything) it sometimes happen that fragment is not attached. I am getting that error in production as well so it is not just my device.
Good practice for that would be to check with method isAdded() and if activity is not attached then do nothing. I am not sure why this is good practice because users would not be happy if they loose their work since "save dialog" will not be shown just because Google is giving good practices as terrible ones!
So far I have this (among million combinations) inside my fragment:
private void showStoreDialog() {
if (someConditionIsOk) {
StringBuilder sb = new StringBuilder();
sb.append(getString(R.string.someText));
sb.append(MINIMUM_LENGTH);
sb.append(getString(R.string.someText2));
Toast.makeText(getActivity(), sb.toString(), Toast.LENGTH_LONG).show();
return;
}
...
Exception occures on this line: sb.append(getString(R.string.someText));
Cause: android.support.v4.app.Fragment.requireContext (Fragment.java:614)
android.support.v4.app.Fragment.getResources (Fragment.java:678)
android.support.v4.app.Fragment.getString (Fragment.java:700)
solutions.lunalabs.gpsracer.fragments.RecordingFragment.showStoreDialoglog (RecordingFragment.java:152)
I know I could solve this by routing through activity with callbacks but I do not want this because I should route all dialogs through activity even if it is needed only in one fragment. Is there a safe solution to this?
Thank you!
I have an app with a Home screen that has 2 fragments (for now) and a navigation drawer. Currently I load the fragment A (Explore) on startup and load fragment B when clicked. From then on, I show and hide fragments. It's faster than recreating fragments on every click and my fragment A takes some time to load.
I've noticed that when I go to fragment B and go to another activity (let's call it activity 2) from there and leave the app and wait for it to be killed (or do something crazy like change the device language), and then come back to the same activity, it's still there. When I press back to go back to fragment B, sometimes (50% of times) the fragment B is drawn over fragment A. On clicking fragment A in the drawer, fragment A appears fine, but on clicking fragment B, there's another instance of fragment A and on top of that fragment B.
I've spent more than 2 days on this problem and got nowhere.
Here's my code for selecting the fragment:
private void selectItem(int position, boolean addExploreFragment) {
Log.d(tag, "selectItem: " + position);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
//add explore fragment - this is called on app startup, but also when the app is killed and resumed which results in 2 explore fragments
if (addExploreFragment){
fragmentTransaction.replace(R.id.content_frame, mExploreFragment, EXPLORE_FRAGMENT_TAG);
Log.d(tag, "Replaced frame and added "+ mFragmentTags[position]);
} else {
//add fragment for the first time
if (getSupportFragmentManager().findFragmentByTag(mFragmentTags[position]) == null && position != 0) {
fragmentTransaction.add(R.id.content_frame, mFragments[position], mFragmentTags[position]);
Log.d(tag, "Added Fragment: "+ mFragmentTags[position]);
}
//shows and hides fragments
for (int i = 0; i < mFragments.length; i++) {
if (i == position) {
fragmentTransaction.show(mFragments[i]);
Log.d(tag, "Showing Fragment: "+ mFragmentTags[i]);
} else {
if (getSupportFragmentManager().findFragmentByTag(mFragmentTags[i]) != null) {
fragmentTransaction.hide(mFragments[i]);
Log.d(tag, "Hid Fragment: "+ mFragmentTags[i]);
}
}
}
}
fragmentTransaction.commit();
//not null check for calling selectItem(0) before loading the drawer
if (mDrawerList != null){
mDrawerList.setItemChecked(position, true);
}
}
I know for sure, the explore fragment is getting created twice and the two instances behave independently of each other (just sharing).
I'm lost what to do next. This is an issue which can be reproduced very easily on low end devices but on a device like Nexus 4 (my test device), the issue can be reproduced by changing the device language.
Has anyone got any ideas about this? Basically if the addExploreFragment block doesn't get called when there is already an exploreFragment, this issue could be solved, I think, but I've been unable to do so. Also, I tried removing all the fragments and then adding the exploreFragment but same thing happens (50% of times).
Thanks! and sorry for the long post, I felt I should share all the details.
Update: When I change the device language and come back to the app on Activity 2 and go back to Home activity, it has the fragment B open which is good, but fragment A get recreated because it's a heavy fragment and the system probably removed it from memory. Again, that's ok that it gets recreated IF it got removed by the system but why does it get recreated when it's not removed. I believe it's something with my code, on every 2nd attempt (without closing the app) this happens, 2 instances of the heavy fragment A. Out of ideas.
But shouldn't fragmentTransaction.replace remove all the previously added fragments and then add exploreFragment. It's not working like that. Neither fragment A nor Fragment B are getting removed.
I found out something new and rather odd to me. When you use fragmentTransaction.add, the listeners you have, like DrawerItemClickListener, on the previous fragment, are still active. And this is even if you use fragmentTransaction.commit.
So...I suspect when the add method is used, you actually clicked on another hidden button or hidden UI that has an event listener on the previous fragment. I don't like this of course and the effect may be very confusing. Yes, this happened to me and I didn't understand why for a while.
For now, I think the easiest code fix would be to use the replace method instead of add. The replace() makes listeners inactive. If it works, then you can make a better/elegant fix.
Let me know what happens....
I started to notice your post
when I go to fragment B and go to another activity
When you interact or start another Activity, you start a new set of Fragments. Look at this Google webpage # Fragments Lifecycle.
For clarification of my claim, there is a quote saying
A fragment must always be embedded in an activity and the fragment's
lifecycle is directly affected by the host activity's lifecycle.
You might as well read few paragraphs of it, at least.
I am not sure what your solution should be. Perhaps make the fragments distinctive, different and clear between the two Activities you have.
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.
Is there some way I get already created currently displayed same instance of fragment in my activity. I DON'T to use
findFragmentById(int id), simply I never created that
findFragmentByTag(String tag), because I am not adding tag in every fragment .offcourse due to some requirement.
getFragment(Bundle bundle, String key), because I never am putting in bundle.
Although I may look like fool to mention that, but I want something like this. Is activity keep some fragment instance somewhere.??
What can be the best approach I can take to achieve this requirement.
UPDATE
Okay, so let me tell you why I can't use above methods. If I am adding various fragment in one activity, where I always want to come back to one fragment when back is clicked. (As we have in navigation drawer, u know). And unless there are inner fragment. so for that I don't want to add in the back stack.
Now even if I have the tag associated with my fragments, I cant say for 8 fragment if- else-if-else for getting the tag. That I know is not correct. So first two ways goes out of my option. Now third one. I exactly don't know where to keep it. And even if I keep it where will I get the bundle and key every time I just want my fragment.
You can get from fragment Manager
List<Fragment> fragList=fManager.getFragments();
for(Fragment fr: fragList){
String fragClassName = fr.getClass().getName();
if(fragClassName.equals(Abc.class.getName())){
Log.i("Fragment:","Abc");
}else if (fragClassName.equals(Xyz.class.getName())) {
Log.i("Fragment:","Xyz");
}
}
I have an application where whenever I exit the application via the home hardware button, it should return to the last state the application is in. However, when I launch the application again, the application shows a white screen with only my header bar. And when I click on the header bar's button, the application crashes with the IllegalStateException where the application cannot find the method for the button clicked.
I am currently implementing with Sherlocks Fragment, where the header bar is an action bar. I'm also using HTC Rhyme, Version 2.3 (Gingerbread). The following is the codes for the addition of fragments into my main app.
Codes to add the fragments within the onCreate method in the activity:
FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
Bundle bMain = getIntent().getExtras();
String statusCheck = "";
if (bMain != null) {
statusCheck = bMain.getString("statusCheck");
}
if (statusCheck.equals("web")) {
MyWebViewFragment webfrag = new MyWebViewFragment();
trans.add(R.id.container,webfrag, "WebViewFragment");
} else if(statusCheck.equals("traveloguelist")) {
MyTravelogueListFragment travelfrag = new MyTravelogueListFragment();
trans.add(R.id.container,travelfrag, "TravelogueListFragment");
}
trans.commit();
This is the codes when I change a fragment:
MyTravelogueListFragment travelfrag = new MyTravelogueListFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.container, travelfrag).addToBackStack(null).commit();
[Edit]
I realized after much reading and running that the main issuei have is that upon resuming the application, the activity is actually created again. Thus, some of the parameters i passed in does not get registered, resulting in the wrong display. I THINK this is the error that is causing that to happen:
Previously focused view reported id "myresID" during save, but can't be found during restore.
However, I don't know how you force the application to remember the previous state of the fragment? Or is there any other way around this problem?
I'm still very stuck with this problem. Will really appreciate it if someone can help me!
After much trial and error and many readings, I finally found a way to sort of solve my problem.
From what I understand, this problem will occur due to the Activity's life cycle. The comment by Tseng in this forum was quite comprehensive:
http://forum.unity3d.com/threads/127794-Android-Apps-crashing-on-resume
It seems that during the time when other applications are invoked when a certain activity is onPause/onStop, Android might free up some of its memory the activity is currently holding on to if there is insufficient memory required. In this case, all the current objects or variable the paused activity is having will be destroyed. Thus, when the activity is back on focus, the onCreate is actually invoked again. Thus, the activity will have no idea which fragment I am currently require.
However, I realized that it will always call the saveInstanceState which is essentially a bundle object. So I did the following:
onSaveInstanceState method
#Override
public void onSaveInstanceState(Bundle bundle) {
//activityFrag is a string object that tells me which fragment i am in currently
bundle.putString("statusCheck", activityFrag);
}
onCreate method
if (savedInstanceState != null) {
getSupportFragmentManager().popBackStack(null, getSupportFragmentManager().POP_BACK_STACK_INCLUSIVE);
//return;
statusCheck = savedInstanceState.getString("statusCheck");
} else {
statusCheck = b.getString("statusCheck");
}
What I have done is to remove all the fragments I have stacked thus far to remove any issues where there is missing information needed. So this is like starting anew again. The status check just determine which fragment the user has last visited.
After much testing, it seems like it does solve my problem. though I wouldn't say it is perfect. One of the main downfall I have is that whenever I change my fragment, I have to update and change my statusCheck to make sure the correct fragment will be called. However, I have to admit this way is a little unorthodox and might not be very correct.
If any of you have any better ideas, please feel free to share!
You can try to implement following:
Use putFragment to save all fragments, currently located in FragmentManager, into bundle in onSaveInstanceState;
And then you can use getFragment to get all previously stored fragments back from bundle in onRestoreInstanceState.
Also... you'll probably need some HashMap that will help to determine the hierarchy of the fragments (in case you have containers and contained fragments) to be saved into bundle as well.
Also... when restoring from bundle you'll need to know keys for all fragment you've put there earlier. Probably, the easiest way is simply to organize an array of keys and put them into bundle when saving the fragment into instance.
This way your saving and restoring will be complete and centralized.