FragmentManager.popBackStack() does not simply loads the previous Fragment - android

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

Related

What Fragments am I hosting and displaying?

Is there a way to know which Fragment is currently displayed in a given <fragment> container of an Activity without keeping track of all the changes via the onAttachFragment callback?
Is it even possible to know which fragments are displayed when fragment transactions can take place when the user presses the back key? In this latter case, i.e. when a Fragment is re-displayed due to a back, the onAttach is not called.
In my experience, the only way to know for sure which fragment is being displayed is to keep track of that carefully yourself.
For example, you could make a variable in your Activity:
Fragment mCurrentDisplayedFragment;
and then whenever the user requests a different fragment do:
mCurrentFragment = (Fragment) userRequestedFragment;
fragmentManager.replace(container, mCurrentFragment, tag);
Then, whenever you needed to do things to the currently displayed fragment, you could triage it by try/catching a cast or with instanceof.
You could also handle the back pressed behavior by overriding that method in the activity:
#Override
public void onBackPressed() {
int stackSize = fragmentManager.getBackStackEntryCount();
// This counts up from the bottom so the most recent fragment is the biggest number/size
backFragId = fragmentManager.getBackStackEntryAt(stackSize);
// Get a handle on the fragment that is about to be popped
mCurrentFragment = fragmentManager.findFragmentById(backFragId);
super.onBackPressed();
}
Also, are you sure that onAttach is not called when a fragment is popped off the stack? I seem to remember that it will be, and you can call through the interface created there (if you have one and the activity implements it) to register the fragment as the current fragment in the activity at the time.
But to directly answer your question, there isn't a built in way to just know what fragment is currently displayed (and there could be more than one!). The implementation details of that are up to you. Hopefully I've given you some ideas of how it could be handled though. You might also find the FragmentManager documentation helpful.
Each time when you add/replace fragment to the container, use tag for it:
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.add(R.id.container, fragment, tag).commit();
then you can find out the fragment is current visible or not:
Fragment fg = getFragmentManger().findFragmentByTag(tag);
if(fg.isVisible())
//fg is the current visible fragment
Hope this help!

Using Fragments before they are attached

I am creating a master/detail type application. The Master view is a MasterFragment that shows a list of Master items, while the Detail view is a DetailsFragment that shows a list of Detail items.
When a user clicks a Master item in the list, I create a new DetailsFragment and show it using a transaction.
The details shown in a DetailFragment take some time to load (seconds) so I want to load them in a background thread and show the list once the loading is finished.
I now want to give the user the option to long-click a Master item, which (instead of opening it immediately and letting him wait) will create the DetailsFragment in the background (not visible yet), allowing him to browse the MasterFragment while it's loading. A navigation item is added to the Navigation Drawer so he can navigate to the DetailsFragment after some time when it has finished loading.
Think of it like using a web browser on very slow internet - instead of opening a page and waiting for it to load it is much nicer to open a page in a new tab in the background, browsing the current page some more while it loads, and then going back to the new tab when you think it must be finished loading. That's what I want to do in my app as well except with Fragments.
Now I learned that with creating Fragments it's important to use a static factory method that creates the Fragment, adds any objects as arguments to a Bundle, and then leave only an empty constructor.
public class DetailsFragment : Fragment
{
public DetailsFragment()
{
// Leave empty
}
public static DetailsFragment create(int masterId)
{
DetailsFragment f = new DetailsFragment();
Bundle args = new Bundle();
bundle.putInt("MasterId", masterId);
f.setArguments(args);
return f;
}
#Override
private void onCreate(Bundle bundle)
{
super.onCreate(bundle);
// Get master ID
int masterId = getArguments().getInt("MasterId");
// Load details in background thread
load(masterId);
}
#Background
private void load(int masterId)
{
//... (loading takes a few seconds...)
loadFinished();
}
#UiThread
private void loadFinished()
{
// update view...
}
}
(Note: I am using Android Annotations so that the 'load' method (with the #Background annotation) is run in the background. Just pretend I start it using a runner or AsyncTask or whatever.)
There is a problem here however: onCreate is not called until the Fragment is 'called upon', in other words there is no loading being done until the user opens the details fragment. I have tried onAttach instead of onCreate but the same thing happens. It seems onAttach is the first method called in the lifecycle and that is already too late.
I want the loading to start immediately, even if the Fragment is not shown yet (it may never be shown if the user doesn't navigate to it anymore).
How can I implement this behavior?
This is how I preload a fragment. Not sure it is the best way or the android way but it's the way I figured out how to do it.
In my layout xml I have the following:
<FrameLayout
android:id="#+id/master_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible" />
<FrameLayout
android:id="#+id/details_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"/>
Notice that the "details_frame" is invisible. Then when you want to preload the fragment , you replace the invisible details_frame with the fragment and it will remain invisible:
getSupportFragmentManager().beginTransaction()
.replace(R.id.details_frame, <DetailsFragment>, <FragmentName>)
.commit();
Then once you want to display it you change the visibility to visible instead of invisible.
findViewById(R.id.details_frame).setVisibilility(View.VISIBLE);

Android Fragment View State Loss When Using FragmentTransaction.replace()

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.

Android fragment instances avoid duplicates

Hi I am not sure I am doing the right thing. I have several fragments in one activity (not shown at the same time). When I add the fragment do I have to check if a previous instance exists? I am using the compatibility package and my fragment CameraFragment is a separate class (in its own file):
private void addNewFragment(Fragment fragment, String tag) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.frag1, fragment, tag);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(null);
ft.commit();
}
and then :
public void startPicTaking() {
addNewFragment(CameraFragment.newInstance(), TAG_PIC_TAKING);
}
So each time a user clicks a button to take a picture I use this methods BUT shall I verify if the fragment already exists and remove it first or does the static method newInstance make sure the fragment is not duplicated?
I have read the doc several times but I don't understand why the line:
ft.addToBackStack(null);
what is it for? I know you can pop the back stack and it keeps the transaction but how can it be used and for what? Is it necessary or if I don't use it I can skip it?
thanks
I have several fragments in one activity (not shown at the same time). When I add the fragment do I have to check if a previous instance exists?
No, it will just create a new instance of that Fragment when it adds the next instance of it. It will not affect the previous instance of it.
So each time a user clicks a button to take a picture I use this methods BUT shall I verify if the fragment already exists and remove it first or does the static method newInstance make sure the fragment is not duplicated?
You could do that if you wanted to, to ensure that no Fragment appears twice in the stack. (So when you hit back, you don't get the same activity again.) Depending on exactly what appears in your back stack, you may not want to remove stuff lower down. (Consider that a user expects previous fragments to appear when he hits the back button.)
I have read the doc several times but I don't understand why the line: ft.addToBackStack(null); what is it for?
When Fragment objects are added to the back stack, then each time the user hits back, they will go to the previous item on the stack. If you do not add an item to the back stack, the user will not encounter it when they hit the back button.

Android: correct way of jumping between fragments

This is a design question, rather than a technical one.
General case: I want an UI event in a Fragment to make Activity-wide changes.
Specific case: I have two fragments, hosted in the same activity. When the user clicks a button in one of those fragments, I want it to be replaced by the other.
I don't want, however, my Fragments touching my activity. I may want to change the behavior later (maybe, in a bigger screen, show both fragments instead of replacing the first), and I don't want my Fragment code to have that logic.
What I did was implement a Listener class in my fragments, that reports events back to the Activity. This way, if I want to use another Activity class with different display behavior, I can just change the listener and leave the Fragment code untouched.
Is this a good way to go about it? Is there a standard good practice, or a better design pattern?
Using listeners is the recommended way of communicating between Fragment and your activity.
See this Android documentatin section for infromation. Long story short they just implement a listener interface by the Activity class and cast getActivity() result in a fragment to a listener.
From my personal experience this is very convenient because lets you to:
Easilly switch underlying activity (e.g. you host entire fragment in a wrapper activity for compatibility in pre-3.0 and host this fragment along with others in 11+)
Easilly control if the wrapper activity supports callbacks or not. Just check is it does implement the listener and do your app specific actions if it doesn't.
You are right on about using a Listener. This is something I also had to deal with in a project at work. The best way to handle it is to make the Fragment stand-alone in nature. Anything wishing to interact with the Fragment should use its public API and/or set listeners for specific events. If you are familiar with Design Patterns, this is the Observer pattern. The events can be general or specific as well as contain data or no data.
As an example of my project, I had two Fragments. A ListFragment and an InfoFragment that displayed the selected ListItem. The ListFragment already has a Listener interface for my Activity to hook into, but the InfoFragment does not since its your basic Fragment. I added a Listener interface to the InfoFragment that would be notified when the Fragment wanted to close. For the Fragment, this could be by a button press, or specific action occured, but as far as my Activity is concerned, when the Event is triggered, it would close up the Fragment view.
Don't be afraid to use a lot of Listeners for Fragments, but also try to group them by a specific action using data parameters to individualize them. Hope this helps!
A technical answer for:
I have two fragments, hosted in the same activity. When the user clicks a button in one of those fragments, I want it to be replaced by the other.
FragmentTransaction ft = this.getFragmentManager().beginTransaction();
Fragment mFragment = Fragment.instantiate(this.Activity(), Fragment2.class.getName());
ft.replace(android.R.id.content, mFragment);
ft.commit();
public class Example_3_Mainfile extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.example_3_mainfile);
Fragment fr ;//make class that extend to thefragment
fr = new Act_2_1();
FragmentManager fm = getFragmentManager();
FragmentTransaction fragmentTransaction = fm.beginTransaction();
fragmentTransaction.replace(R.id.fragment_place, fr);
//id get of fragment tag from xml file there decelar
fragmentTransaction.commit();
}
}

Categories

Resources