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.
Related
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.
Scenario what i'm trying to achieve:
Loading activity with two frame containers (for list of items and for details).
At the app launch time add listFragment in listFrame and some initial infoFragment in detailsFrame containers.
Navigating through list items without adding each detail transaction to back stack (want to keep only infoFragment in stack).
As soon as user hit back button (navigate back) he falls back to intial infoFragment what was added in launch time.
If sequential back navigation fallows then apps exit.
My code:
protected override void OnCreate(Bundle savedInstanceState)
{
...
var listFrag = new ListFragment();
var infoFrag = new InfoFragment();
var trans = FragmentManager.BeginTransaction();
trans.Add(Resource.Id.listFrame, listFrag);
trans.Add(Resource.Id.detailsFrame, infoFrag);
trans.Commit();
...
}
public void OnItemSelected(int id)
{
var detailsFrag = DetailFragment.NewInstance(id);
var trans = FragmentManager.BeginTransaction();
trans.Replace(Resource.Id.detailsFrame, detailsFrag);
if (FragmentManager.BackStackEntryCount == 0)
{
trans.AddToBackStack(null);
}
trans.Commit();
}
My problem:
After back button has been hit, infoFrag is overlapped with previous detailFrag! Why?
You can do this:
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
getSupportFragmentManager().popBackStack(getSupportFragmentManager().getBackStackEntryAt(0).getId(), getSupportFragmentManager().POP_BACK_STACK_INCLUSIVE);
} else {
super.onBackPressed();}
In your activity, so you to keep first fragment.
You shouldn't have, in your first fragment, the addToBackStack. But, in the rest, yes.
Very nice explanation by Budius. I read his advice and implemented similar navigation, which I would like to share with others.
Instead of replacing fragments like this:
Transaction.remove(detail1).add(detail2)
Transaction.remove(detail2).add(detail3)
Transaction.remove(detail3).add(detail4)
I added a fragment container layout in the activity layout file. It can be either LinearLayout, RelativeLayot or FrameLayout etc.. So in the activity on create I had this:
transaction.replace(R.id.HomeInputFragment, mainHomeFragment).commit();
mainHomeFragment is the fragment I want to get back to when pressing the back button, like infoFrag. Then, before EVERY NEXT transaction I put:
fragmentManager.popBackStackImmediate();
transaction.replace(R.id.HomeInputFragment, frag2).addToBackStack(null).commit();
or
fragmentManager.popBackStackImmediate();
transaction.replace(R.id.HomeInputFragment, frag3).addToBackStack(null).commit();
That way you don't have to keep track of which fragment is currenty showing.
The problem is that the transaction that you're backing from have two steps:
remove infoFrag
add detailsFrag (that is the first1 detail container that was added)
(we know that because the documentation This is essentially the same as calling remove(Fragment) for all currently added fragments that were added with the same containerViewId and then add(int, Fragment, String) with the same arguments given here. )
So whenever the system is reverting that one transaction is reverting exactly those 2 steps, and it say nothing about the last detailFrag that was added to it, so it doesn't do anything with it.
There're two possible work arounds I can think on your case:
Keep a reference on your activity to the last detailsFrag used and use the BackStackChange listener to whenever the value change from 1 to 0 (you'll have to keep track of previous values) you also remove that one remaining fragment
on every click listener you'll have to popBackStackImmediatly() (to remove the previous transaction) and addToBackStack() on all transactions. On this workaround you can also use some setCustomAnimation magic to make sure it all looks nice on the screen (e.g. use a alpha animation from 0 to 0 duration 1 to avoid previous fragment appearing and disappearing again.
ps. I agree that the fragment manager/transaction should be a bit more clever to the way it handles back stack on .replace() actions, but that's the way it does it.
edit:
what is happening is like this (I'm adding numbers to the details to make it more clear).
Remember that .replace() = .remove().add()
Transaction.remove(info).add(detail1).addToBackStack(null) // 1st time
Transaction.remove(detail1).add(detail2) // 2nd time
Transaction.remove(detail2).add(detail3) // 3rd time
Transaction.remove(detail3).add(detail4) // 4th time
so now we have detail4 on the layout:
< Press back button >
System pops the back stack and find the following back entry to be reversed
remove(info).add(detail1);
so the system makes that transaction backward.
tries to remove detail1 (is not there, so it ignores)
re-add(info) // OVERLAP !!!
so the problem is that the system doesn't realise that there's a detail4 and that the transaction was .replace() that it was supposed to replace whatever is in there.
You could just override onBackPressed and commit a transaction to the initial fragment.
I'm guessing but:
You've added the transaction to replace infoFrag with 1st detailsFrag into the backstack.
But then you replace 1st detailsFrag with 2nd detailsFrag.
At this point when you click back, the fragment manager cannot cleanly replace 1st detailsFrag with infoFrag as 1st detailsFrag has already been removed and replaced.
Whether the overlapping behaviour is expected or not I don't know.
I would suggest debugging the Android core code to see what it is doing.
I'm not sure whether you can achieve without say overriding Activity::onBackPressed() and doing the pops yourself having added all transactions to the backstack.
I have a problem with a fragmented layout and I sincerely apologize if it has been answered before and I was too dumb to find it. I searched for hours and got nothing (well, I got lots but nothing solved my problem).
So here's my setup: I have a two pane layout using two FrameLayouts as containers for my fragments. activity_listing.xml:
<FrameLayout android:id="#+id/listing" />
<FrameLayout android:id="#+id/details" />
On opening the app (onCreate in the fragment's activity) a fragment called Listing is added to FrameLayout "listing" programmatically using a FragmentTransaction.
public class ListingActivity extends FragmentActivity
implements ListingFragment.Callbacks {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_listing);
// Displaying the first listing here ...
Fragment fragment = new ListingFragment();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.listing, fragment);
ft.addToBackStack(null);
ft.commit();
...
}
This ListingFragment is replaced a few times during runtime, so I have to create it instead of defining it in XML. I tried the latter with
<fragment android:name="com.example.app.ListingFragment" />
but then it won't be replaced or removed programmatically later on. I can only add() and then the old one is visible through the new one. But that's not the problem here - just an explanation for my way.
So far all of this works as it should, Listing is created and displayed etc., so no problems there. But:
When ListingFragment is displayed at startup and I press the back key, at the last position FrameLayout "listing" is emptied instead of dropping back to the Homescreen! I figured it has to be because onCreate of my ListingActivity I display an empty frame and add() a ListingFragment to it. So the back stack has the empty frame in it, too. Right?
I tried solving the situation by this to ListingActivity:
#Override
public void onBackPressed() {
super.onBackPressed();
if(getSupportFragmentManager().getBackStackEntryCount() == 0) {
this.finish();
}
}
But somehow that does not look or feel right ... it looks like a bad work around.
So is there any way to insert the fragment before the view with the empty FrameLayout is inflated so there is no empty state to "back" to? Or is it possible to remove the "empty" state from the back stack even though it is not in it? Any other ideas on how to avoid the empty frame after hitting "back"?
Thank you very much for your efforts in advance!
Don't call ft.addToBackStack(null) when you add the Fragment in onCreate. That tells the FragmentManger that you have another state BEFORE that fragment that you want to be able to jump back to.
I have a main TabActivity which has two tabs, A and B (for now). Tab A loads a FragmentActivity (code given below) which just conatains a FrameLayout, so I can load my Fragments for that specific Tab in it.
The first Fragment has some TextViews and one ListView. Data is pulled from a web service. When I click on an ListView's item, I load that item's detail in another Fragment (this also comes from a web service) and replace the current Fragment (with ListView and other controls) with another detail fragment.
To achieve this, I am using android-support-v4.jar library to use Fragments as they were preferred.
Tab A's FragmentActivity's XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<FrameLayout
android:id="#+id/updates_frame"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#drawable/background"/>
</LinearLayout>
Tab A's FragmentActivity Java code:
public class UpdatesFragmentActivity extends FragmentActivity implements
IUpdateNotifier {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.updates);
//Load Initial Fragment into FrameLayout
//I am adding this Fragment to BackStack
Fragment newFragment = new UpdatesFragment();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.addToBackStack(null);
ft.add(R.id.updates_frame, newFragment);
ft.commit();
}
//This is an Interface method which I call with the clicked "FEED" object to load its detail in another Fragment
#Override
public void onFeedSelected(Feed feed) {
// Instantiate a new fragment.
Fragment newFragment = new FeedDetailFragment(feed);
// Add the fragment to the activity, pushing this transaction
// on to the back stack.
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.updates_frame, newFragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(null);
ft.commit();
}
//This is another Interface Method which I call when the user presses "BACK".
//I am trying to load the previously loaded Fragment, which I added to BackStack.
//But this causes reconstruction of the previously loaded fragment. LIST in this case
//which call the web service. I DONT WANT TO CALL SERVICE AGAIN.
#Override
public void onBackPressed() {
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
fm.popBackStack();
}
}
}
I have created an interface IUpdateNotifier, which contains two methods:
public void onFeedSelected(Feed feed);
public void onBackPressed();
Parent UpdatesFragmentActivity implements these methods. I call these methods from children Fragments upon following actions.
I call onFeedSelected(Feed feed) from the Fragment which has a ListView. I send the clicked feed item to parent FragmentActivity, so it loads another Fragment which would contain that feed detail.
I call onBackPressed() from the second feed detail Fragment when the user presses a button that is supposed to bring back the first fragment which contained ListView with other controls. As you can see, I try to call FragmentManager's popBackStack() method to bring back that first Fragment...
But the first Fragment gets refreshed and loads all the data from web service.
Actually I cannot get and store data only once nor the updates are frequent on some time intervals. The user can update the list when he wants. Initially, the list loads the top 10 items from the service, and then user can click the "More" button at the end of list if he wants to load more items.
It will load the next 10 items and so on. But I think I can store the retrieved ArrayList in some variable in UpdatesFragmentActivity and then just reassign that ArrayList to the list's adapter instead of loading the data from service, but I don't know how to make Fragment not to call service again.
I want it to behave like when I click on tab 2 and then on tab 1 again. It simply shows the loaded data as if was hidden and does not call the service.
How can I achieve this?
Your design pattern is flawed due to a poor separation of concerns. The updating of data should be decoupled from the UI, therfore when a user goes back to the previous Fragment it should have nothing to do with loading data from a web service.
There are a couple of easy fixes but I do not know what will work best as you have given little context to the problem.
First option would be to introduce a Splash Screen on start up. This Activity would make use of an AsyncTask to download the data you need from the web service. This works well if you only want the data to be downloaded once during the runtime of the app. You would make sure not to add this Activity to the history so when back is pressed from the next activity, the app would then exit.
Another option, which I have used in many apps and the one I prefer, is the use of Alarms via the AlarmManager. You can set a periodic updates at specific time intervals, the AlarmManager even helps you to the point where it contains enumerations of time. The Alarm will trigger a broadcast receiver which will execute your custom code, that will download the data you need from the web service and store it.
There is a tutorial on this approach, which can be found here http://android.arnodenhond.com/tutorials/alarm-notification.
Finally; you should not need to pop the back stack to get around this problem, although you might be doing this for entirely different reasons but it is hard to tell without more info.
Your question is not clear enough, ask more simple and precise questions... And as you say above
popBackStack does not load the last fragment, it's commonly used to pop the entire stack :
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
before loading another fragment
beginTransaction()
replace() Or add()
commit()
That's all i can think about with that question
Good luck
Assume I have an Activity which contains two FrameLayouts (let's call them FrameA and FrameB) which in turn each contain a Fragment (let's call them FragmentA1 and FragmentB1 respectively). Now, I commit a series of individual fragment transactions using code similar to the following...
getFragmentManager()
.beginTransaction()
.replace(frameId, fragment)
.addToBackStack(null)
.commit();
... such that I replace FragmentA1 in FrameA with FragmentA2, then I replace FragmentB1 in FrameB with FragmentB2, then I replace FragmentA2 in FrameA with FragmentA3, then I replace FragmentB2 in Frame2 with FragmentB3, and the final state looks like the picture above (where only FragmentA3 and FragmentB3 are visible).
If I understood correctly how the back stack works, pressing 'back' will interleave popping of the Fragments between FrameA and FrameB (reflecting how I added them).
Does anyone know if it is possible to pop the last transaction on FrameA or FrameB selectively? (i.e. if I pressed 'Pop FrameA' then FrameA would be transitioned back from FragmentA3 to FragmentA2 and, instead, if I pressed 'Pop FrameB' then FrameB would be transitioned back from FragmentB3 to FragmentB2)
Supplement: I know I can get the Fragment last added to a given FrameLayout using the FragmentManager.findFragmentById(int framelayoutId) method, but calling FragmentTransaction.remove(fragment).commit() only removes the Fragment from the View and does not transition the View back to the Fragment it previously displayed.
Basically, no, there is only one back stack for an activity.
You will just need to implement your own separate back stacks.
As of Android 4.0 (and the associated support library) there are APIs that should make this relatively easy -- FragmentTransaction.detach(Fragment) lets you put a fragment into the same state it is when in the back stack, and FragmentManager.saveFragmentInstanceState(Fragment) lets you go further and completely throw away the Fragment object. Not coincidentally, these are used to implement ViewPager's FragmentPagerAdapter and FragmentStatePagerAdapter, respectively, so you could look at the code for these as an example of how to use them.
FragmentManager.popBackStack(String name, FragmentManager.POP_BACK_STACK_INCLUSIVE)
Here is the simplest answer, and the explanation is very clear: Well there are a few ways to go about this depending on the intended behavior, but this link should give you all the best solutions and not surprisingly is from Dianne Hackborn...