I have app that is primarily for viewing documents. The documents are displayed in an html WebView and can are placed in a tabbed view so the user can swipe left/right to view the documents on their current page.
The users have the option to change the text size and the appearance of the documents they are viewing (night, light, and sepia mode), so basically a simpler version of the same idea as the kindle appearance editor. The appearance options come up as a pop-up window over their current document. I want them to be able to select an option and instantly see the change.
As my app is now, when the user selects one of the appearance options, all of the fragments are "refreshed" using the below code. All of the pages on the current activity successfully update such that I can swipe between documents though the tabbed view and see the selected change in all of them. The issue I have with my implementation is that the "refresh" caused the current fragment/activity to "blink." I believe this is due to detaching, attaching, and then committing the fragments on the current activity.
Bellow is my current implementation of refresh...
/***********************************************************************************************
* Refreshes all the fragments (tabs) of the current Document activity.
***********************************************************************************************/
public void refreshAll()
{
final FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
SparseArray<Fragment> fragmentSparseArray = adapter.getRegisteredFragment();
for(int i = 0; i < fragmentSparseArray.size(); i++)
{
ft.detach(fragmentSparseArray.get(i));
ft.attach(fragmentSparseArray.get(i));
}
ft.commit();
}
Here is what I have tried...
I've tried replacing the detach and attach with...
ft.replace(R.id.htmlView, fragementSparceArray.get(i), "htmlView")
But I couldn't get it to work with that although I think that may be because I am using it wrong...
I have also tried to using overridePendingTransition(0,0) in a number of different places including...
Before and after ft.commit()
overridePendingTransition(0,0);
ft.commit();
overridePendingTransition(0,0);
Before and after the detach and attach...
overridePendingTransition(0,0);
ft.detach(fragmentSparseArray.get(i));
ft.attach(fragmentSparseArray.get(i));
overridePendingTransition(0,0);
And a combination of both... I have also tried applying...
addToBackStack(null)
after detaching and attaching.
The last thing I tried to do is add...
android:animateLayoutChanges="false"
to the fragment and ViewPager xml layouts.
None of those made any changes. I will be interested in any recommendations!
Thank you.
Related
Actual problem / TL;DR: Except when using one way I consider a hack, I can't pop the activity's entire backstack to return to my initial state with a single fragment added during onCreate(). The Fragment is present in the Activity State Manager dump, but is not visible, leaving me with an empty screen.
I have encountered this issue today and am mostly trying to understand whether this is caused by a bug or by my misunderstanding of the FragmentManager's BackStack. I want to make sure I'm not misusing Fragments or building on shaky API foundations.
I have an activity which essentially offers a descending "tree" navigation as a grid of buttons where each button opens either a sub-grid (e.g. sub-category with more buttons) or some custom form. The content of the forms is irrelevant, but users are expected to repetitively fill them in various orders.
I use Fragments from the support library (support-v4:25.1.1) and whenever a new "screen" is required (either for a form or a sub-grid) it will be added using a transaction on the activity's FragmentManager similar to:
/* adding a new screen, going further down our nav tree */
fragmentManager
.beginTransaction()
.addToBackStack("...")
.replace(R.id.container, newFragment)
.commit();
Every transaction looks like that except the setup of the initial state of my activity, which adds the initial "root grid" fragment without adding the transaction to the backstate.
/* adding the initial fragment during the first execution of activity's onCreate(). */
fragmentManager
.beginTransaction()
.replace(R.id.container, rootFragment)
.commit();
The reason I'm posting here is that I encountered a very strange behaviour when attempting to pop the entire BackStack, which is supposed to bring the user back to the initial state of the activity which has the root grid (the one added in the code above). Almost every technique I tried effectively clears the backstack, but leaves an empty screen instead of my initial main-menu grid. After looking around on multiple questions here and the docs, I tried multiple solutions and only this hack-looking one has worked:
int remainingEntries = fragmentManager.getBackStackEntryCount();
while (remainingEntries-- > 0) {
fragmentManager.popBackStackImmediate();
}
I say this seems like a hack because it requires me to immediately (synchronously) pop an arbitrary number of backstack entries to reach the root, whereas from my understanding it should be possible to do that with a single asynchronous method call:
fragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
or as I've also seen posted around here:
int id = fragmentManager.getBackStackEntryAt(0).getId();
fragmentManager.popBackStack(id, FragmentManager.POP_BACK_STACK_INCLUSIVE);
This is only an issue with the root fragment, probably because the transaction which adds it to the view is not added to the backstack, since that would cause an extra empty screen to be shown when the user presses the back button to close the activity.
More details:
According to the Activity State Manager dump my fragment still has its view, but has both mFragmentId and mContainerId set to #0.
I do add another fragment I haven't discussed here which retains its instance, but it is only used to hold some data and has no view whatsoever.
There is a PopBackStackImmediate bug in support library version 25.1.0 and above.
Since this issue has been reported either you have to wait for solution, Or you can downgrade the support library version to 25.0.1 and get the expected behavior.
UPDATE
Looks like this bug has been resolved in support library version 25.3.1. Update your library to version 25.3.1.
So I am trying to integrate Google Maps into my application, I came across a concept that I don't entirely understand. I have seen that adding google maps into an app and it seems the most common way to do so is with an activity.
I found some websites and a SO question showing how to put Google Maps in a fragment, but would that be an issue if the user is constantly clicking on profiles and going back? Causing the map to be recreated or resumed constantly. Would that performance be better if the map was an activity instead?
Basically, Im not sure the best way to transition from an activity GUI to a fragment? I've had an app that only used 1 activity, I just used multiple different fragments changing them with this code
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
Fragment fragment = getFragmentManager().findFragmentById(R.id.framecontainer);
if (fragment == null) {
ft.add(R.id.framecontainer, frag, tag);
} else {
ft.replace(R.id.framecontainer, frag, tag);
}
ft.addToBackStack(null);
ft.commit();
I am confused because when I created my main activity, I called
setContentView(R.layout.baselayout);
This layout contained only a frame container, I would then add in a HomeScreenFragment right away and when I needed to change, I would use the FragmentTransaction code above.
However, if my new MapActivity used setContentView(R.layout.maplayout); how would I best change screens? If R.id.maplayout does not contain a framelayout, is it best to start a new activity that uses many fragments like the one I mentioned before? I remember hearing that calling setContentView more than once or outside onCreate() is bad practice.
It seems I am missing something because so far it seems like there is 2 ways of using activities with different layouts
Starting a new activity every time and just trying to minimize the amount of activities.
Make an activity with a FrameLayout and just swap fragments everytime
To address my actual problem
I want my users to click another user marked on the map which will bring them to a profilelayout and view that user's profile, should I use one of the 2 methods above or how should I go about doing so?
Do you guys have any input to point me in the best direction? Thanks!
If your current application is already fragment heavy, have you considered using MapFragment? I think if you're cleaning up your objects/resources appropriately it shouldn't really be a performance issue.
Also according to the documentation it says the following about the MapFragment:
It's a wrapper around a view of a map to automatically handle the necessary life cycle needs.
I think it's also good to note that it's possible for you to do layout manipulation by adding/removing views using the LayoutInflater.
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.
I am using Fragments to represents different views in my application. I replace the fragments using the following code when navigating between views:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_left, R.anim.slide_out_right);
ft.replace(R.id.main_linearlayout_fragmentcont, frag);
ft.addToBackStack(null);
ft.commit();
I have run into a number of problems when rotating and the activity is reconstructed. I need to support old versions of android so android:configChanges="orientation" isn't an option. A lot of the issues are due to the nature of how Android saves Fragment state.
These are the problems I am running into:
1) The Fragment transitions don't remember my custom animations for pop events when they are restored automatically after a rotate. They do however remember my BackStack. I know I can write my own back handler that does a replace using animations and get rid of pop all together but I was wondering if there is a way to either reset the animation before calling popBackStack() or a way to have the FragmentManager remember the animations when it auto restores after rotate.
2) The other issue I have is that I have a bunch of child views (linearlayouts) in one of my top level fragment views that contain their own fragments. These child views are created and populated programmatically. When my fragment is recreated after rotation, I programmatically reconstruct the child views in onCreateView of the Fragment Object and I end up with duplicate fragments under each of the child views (1 - I create programmatically and 1 - Android Fragments create from restore). I am assuming this is because I programmatically reconstruct the child views after rotation with the same id. Is there a way to prevent Fragments from being restored? When does Android inject the Fragments from savedState into these views I construct programmatically? How would I prevent this from happening?
3) The above replace code seems to fire onCreateView multiple times for my frag (Fragment) object. This is without rotation and happens when I run the above code only once. Is there a reason that onCreateView of a Fragment would be called multiple times with the above code?
Questions about Fragments:
1) Can I prevent Android from auto restoring fragments when an activity is reconstructed? How would I go about this? Is it based on the ID of the LinearLayout? Could I call removeAllViews of the LinearLayout containing the fragment onStop? That way the view doesn't exist when it saves?
2) Is there a way to add a Fragment to a LinearLayout that I have a reference to but that doesn't have an ID? It appears the Fragment add, replace APIs require an int ID.
Thanks!
1) if you find out how let me know, I'm also pissed off by that
2) you're probably calling add on the FragmentTransaction inside the top level fragment, but the restore operation is also adding, so duplicates! option 1. Use replace instead. option 2. (preferred) Check if(savedInstances==null) { // do transaction } else { //let the system rebuilt it itself}
3) If you're changing the layout (by calling add or replace) of a view that is a part of a fragment, the manager call the method to creates the view again. I'm still not sure if that is a bug or a feature, and if it's a feature why it is. If you find out let me know
1) (supposed to be 4, no?) don't mess with the layouts, if u want to remove, remove them using while(popBackStackImmediatly){}, but if you go deeper and understand what the system is doing, usually there's no reason to not let it do it automatically.
2) (supposed to be 5, no?) if you have a reference you have the id View.getId()
happy coding!
If you are change the orientation of device then check the validation in activity and it also manage the fragment with stack so your flow not damage in that case.
if(savedInstanceState == null) {
mFragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
mFragmentManager.beginTransaction();
FragmentOne fragment = new FragmentOne();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
}
So I have this ClientListView that works great, shows clients, I can click on a client and get their details on the right (in my second fragment). Defined by this layout here:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment class="com.viciousbytes.studiotab.subactivities.ClientListView"
android:id="#+id/client_list" android:layout_weight="1"
android:layout_width="0px" android:layout_height="match_parent" />
<FrameLayout android:id="#+id/client_details" android:layout_weight="1"
android:layout_width="0px" android:layout_height="match_parent"
android:background="?android:attr/detailsElementBackground" />
</LinearLayout>
This works great, but then I realized another activity I had (that was a fragmentactivity displaying a fragment) took up the whole screen, and would be better served being split into two.
So I went about changing some code that displayed this fragment activity originally
void showSessionEdit()
{
...
Intent intent = new Intent(getActivity(), EditSessionActivity.class);
// Send the recipe index to the new activity
intent.putExtra(EditSessionActivity.THE_SELECTED_CLIENT, (int)mClient.getID());
intent.putExtra(EditSessionActivity.SELECTED_SESSION, sessionId);
startActivityForResult(intent, 1899);
....
}
This worked great, brough up my session editor, i click back I get back to my clients and details. Though I realized I want my session editor to work more like my client list /details which has both on same screen. Through lots LOTS of trial of error I finally did replaced the above with this:
void showSessionEdit()
{
...
SessionEdit details = (SessionEdit) getFragmentManager().findFragmentById(R.id.session_edit);
// Make new fragment instance to show the recipe
details = SessionEdit.newInstance(mContext, mIsTablet, (int)mClient.getID(), sessionId);
// Replace the old fragment with the new one
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.client_list, details);
ft.addToBackStack("client_list");
// Use a fade animation. This makes it clear that this is not a new "layer"
// above the current, but a replacement
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
...
//now add another fragment to the right, just to test i dont have my invoice fragment done yet so I just added session again to see if it would display it does.
SessionEdit details2 = (SessionEdit) getFragmentManager().findFragmentById(R.id.session_edit);
// Make new fragment instance to show the recipe
details2 = SessionEdit.newInstance(mContext, mIsTablet, (int)mClient.getID(), sessionId);
// Replace the old fragment with the new one
FragmentTransaction ft2 = getFragmentManager().beginTransaction();
ft2.replace(R.id.client_details, details2);
ft.addToBackStack("client_details");
// Use a fade animation. This makes it clear that this is not a new "layer"
// above the current, but a replacement
ft2.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft2.commit();
...
}
This works well, though i realized that I was replacing not the "div" so to speak on the layout but the fragment itself, so my references to findFragmentById were no longer my client_details type or client_list type but now a SessionEdit type. So after more trial and error i learned to add the tag to addToBackStack(tag) and I could find fragmentsByTag now, this worked well, and back button sorta worked: Clicking back would replace my client_list on the left again so I could click clients and get their details on the right, the problem is, if my client_list was back again the details on the right would still show my session fragment. Also another issue is my clients list was a ListFragment, so when I did the replace, I could still see the list like underneath the new fragment, as if it was celluloid or something. Very strange.
So I made some headway on replacing my original fragments with new ones, but navigating using the back button no longer works "out of the box" like it did when I was just doing the two fragments, and adding new activities onto each other. So how does one go about navigating around multiple fragments? Ideally I would have a SessionInvoiceActivity that would replace both fragments at once (The client list on left, client details on right) with the session on left, invoices on right. And when the backbutton is clicked, id get back to my client list and client details? But this I am not sure how to do and still pass in the information I need. I am also still not clear as to why when I replace the fragment, the original fragment can be seen underneath? Is it a xml layout definition issue?
I am not sure if you have figured this out now, seems I am a summer behind.
I found fragments specified in layouts (in XML) and fragments added with the FragmentManager are difficult to mix.
I am having a similar issue being I add my "Master" Fragment in onCreate(), then when deemed necessary through size or user selection a second fragment the "Details" Fragment is either put where the "Master" Fragment was or is added to a second layout if it is in landscape mode. When the user hits the Back Key it will show a blank screen with no "Master" Fragment. I thought I may have had a work around for this by making the original call to display my "Master" Fragment in the onStart() of the Activity. Yet there seems to be one more gotcha to this. When adding the "Master" Fragment from the onStart() DO NOT add the transaction to the BackStack. That way the onResume() or whatever from your previous Activity will be called instead, thus not leaving an Activity with a blank screen.