I am building a tablet app. In this app there is one activity with two fragments. First fragment is a "known" list fragment which is showing a simple one item layout list from a database query, the second fragment shows the details about the selected record (from the list fragment). The think with the second fragment is that its type depends from the records being showed in the list. For example if the records are customers then the selected customer's details are shown, if they are inventory items the selected item's details are shown etc.
In order to communicate with the Details Fragment I've created an interface which every detail fragment class implements.
The list fragment is "fixed" in the activity from the layout xml. The detail fragment however is created during the activity's creation like this:
super.onCreate(savedInstanceState);
setContentView(R.layout.act_hlpfiles_host);
...
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.laydetailsfragment, FragmentsPool.getHelperFileFragment(501), "recordDetails");
fragmentTransaction.commit();
myDetailsFragment = getFragmentManager().findFragmentByTag("recordDetails");
...
myListFragment = (frg_hlpfiles_lstrecords) getFragmentManager().findFragmentById(R.id.frg_lstrecords);
....
}
The problem with this code is that myDetailsFragment is always null. This is because the fragmentTransaction.commit() does not run immediately but it happens on the main thread the next time that thread is ready (as the android documentation states).
If I create the detail fragment in onStart() and instantiate the list fragment in onCreate everything works ok.
So the question is: How can I be sure that the fragmentTransaction.commit() has commit the transaction so I can do some work with the added fragment? Furthermore is there any way to wait until the commit happens and then continue with the rest of the code?
Try running fragmentManager.executePendingTransactions() after committing your transaction but before finding by tag and see if that works for you.
In Android API 24 FragmentTransaction has synchronous .commitNow() method.
It's in the reference now: https://developer.android.com/reference/android/app/FragmentTransaction.html#commitNow()
On the contrary, .commit() works asynchronously. It just schedules a commit of the transaction.
I was facing a similar issue.
I think the key learning here is using commitNow() instead of commit() with getSupportFragmentManager. This will disallow the main thread from executing until the fragment has been destroyed. It is imperative when building interfaces and using a shared activity. I should know it had me stumped for a while!
Here is a sample code example:
getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentById(R.id.fragment_frame)).commitNow();
"....so I can do some work with the added fragment? Furthermore is there any way to wait until the commit happens and then continue with the rest of the code?"
It all depends on what work you want to do. From your question I see that most of your work code should be in your fragment code anyway, for example when an inventory item is selected.
On the callback when a selection list item is selected (in order to change the details fragment) you'll be able to get hold of the details fragment comfortably enough anyway.
Furthermore, you already have the fragment from the return of FragmentsPool.getHelperFileFragment(501), so I don't see why you need to get the fragment via its tag.
I'm interested to know what work you need to do in onCreate with your added details fragment.
Related
When I start my app it runs an AsyncTask to load up and then in onPostExecute, I then setContentView to the new layout then add a fragment with two buttons offering two modes by an add FragmentTransaction. After one of the two modes is clicked, it then replaces the fragment with yet another FragmentTransaction using the replace method.
If the app crashes it returns to the first screen, loading up the two buttons offering the two modes. In this case if either mode is selected, the second fragment is loaded but is now the background is suddenly transparent showing the two buttons below and they remain clickable. If they are clicked again they properly replace the fragment so that it isn't visible below. This is just weird, I can't understand what could cause this.
I've researched and seen these two similar questions, one and two, which suggested that it might be because the ID is wrong or I have defined the fragment in XML. Neither of these two factors are the case.
My code is shown below:
Below I replace the loading screen.
#Override
protected void onPostExecute(Void result) {
setContentView(R.layout.activity_main_screen);
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
transaction.add(R.id.fragment_container, new ModeFragment())
.commit();
}
After which, when a button is clicked I pass the fragment I wish to replace the current with into this method below:
private void replaceCurrentFragment(Fragment fragment) {
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
transaction.replace(R.id.fragment_container, fragment)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.addToBackStack(null).commit();
}
This works the first time, however if a crash occurs then the app returns to the first fragment and the second time this method is passed, the new replacing fragment is semi-invisible. Clicking the button on the first fragment again calls this method again and it is now fine.
Obviously I don't want the app to crash so this shouldn't occur, but I get this feeling that there's something wrong with how I'm writing my code.
I've had the same problem happen to me, and it was because I loaded a fragment in the OnCreate of my Activity, without checking if there was a savedInstanceState, so android first reopen all old fragments, then do the OnCreate, which added the fragment over the old ones without replacing them so when you navigate to another fragment, it only replaces the top one, but not the bottom one, so you will see the fragments under it.
Might not be exactly the same thing for you, but it might help you figure it out.
I am having a pretty big issue and I am not quite understanding what is happening. I am developing an application that uses Fragments (from the support library) and am using FragmentTransaction.replace() to place new Fragments on to the back stack and replace the old one. The code looks as follows:
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = ft.beginTransaction();
// Animations in my res/anim folder
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.fragment_container, newFragment, tag);
ft.addToBackStack(null);
ft.commit();
This is successful in replacing my fragment. My issue is the following. In one Fragment, I have a list of items that is built from user input. Now, when the user clicks next and then clicks the back button (to return to the list), the list is empty because the view is destroyed. Now, I have noted the following:
onSaveInstanceState is not called. I believe this is because that is only called when the parent Activity tells it to. Based on the docs: " There are many situations where a fragment may be mostly torn down (such as when placed on the back stack with no UI showing), but its state will not be saved until its owning activity actually needs to save its state.". Apparently, performing a replace on the FragmentTransaction is not one of those times. Does anyone have confirmation on this or a better explanation?
setOnRetainInstanceState(true) is not helpful in this situation. Again, I believe this has to do with info from the docs: "Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change)". I am not performing any action in re-creating the activity so this is of no use.
So, I guess my main question is: is there a way to preserve the View state (simply retain the Fragment) when using replace? There is FragmentTransaction.add(), but there are a few issues with this as well. One being that the exit animation is not performed, thus the animation is not correct. Another is that the new Fragment that the old fragment (the one that is being put into a non-visible state) is still clickable. For example, if I have a ListFragment, and I place a content fragment on top of that by using add, I can still click the list items in the ListFragment.
Without being able to see the code of your fragments this is a bit of a guess, but in the past I've run into this same issue and I've found that resetting the adapter in your ListFragment in onViewStateRestored seems to do the trick.
public void onViewStateRestored (Bundle savedInstanceState)
{
super.onViewStateRestored (savedInstanceState);
setListAdapter(new ArrayAdapter(Activity, R.layout.nav_item, objects));
}
Which is weird considering the documentation states that this method is called after onActivityCreated but before onStart. But it seems that it is also called at other times because when the most recent fragment transaction is popped off the back stack this method is called before the previously replaced fragment is displayed. The activity that owns the fragments has not been paused or obscured in any way, so according to the docs onViewStateRestored should not be called since just the fragments were modified. But this seems to work anyway.
It sounds like you simply need to make sure you have properly implemented onCreateView and onDestroyView. The situation you are describing seems to indicate that when the list fragment is put on the back stack (as a result of the replace transaction) Android is calling onDestroyView to free up some resources. However, it apparently has not destroyed the list fragment because when you tap back you are getting back the same instance of the fragment.
Assuming this is all true then, when the user taps back Android will call onCreateView. Any state that you have stored in the fragment's instance variables should still be there and all you need to do is repopulate the view...perhaps set the adapter on the ListView or whatever.
Also make sure your onSaveInstanceState() callback actually does save any instance state that you need to rebuild the view. That way if the fragment actually does get completely destroyed the FragmentManager can restore the state when it needs to recrete the fragment later.
There're two parts to this question.
Suppose we have an Activity and then two fragments: a ListFragment and a Fragment (which will be shown when you click an item from the ListFragment).
Part 1
Where should I close the fragment? By this I mean what would be considered good from a design point of view. I see two options: one declaring an interface in the fragment and having the activity implementing it, let's call it closeFragment(). This would be a way to communicate from the fragment to the activity like shown in the Dev Site. The other one is probably quite simple and is calling getActivity().getSupportFragmentManager() and using the manager to close it.
Part 2
I know how to create a fragment and replace it since it's on the Dev site but I have doubts about closing one. How should I actually close it? Is something like the following code correct? Suppose that the fragment was added to the BackStack.
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
getSupportFragmentManager().popBackStack();
transaction.remove(this);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
transaction.commit();
Thank you very much.
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.remove(this);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
ft.commit();
I would prefer having a dumb fragment , which don't know anything about where it is being used , so that you could use it on any activity you wish , and it would have the precise goal you've set for it . Of course , you can do whatever you wish .
This looks like closing it , but I would prefer replacing it instead . You can also always return to the fragment as long as you have a reference to it .
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();
}
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