onCreateView() isn't called immediately after FragmentTransaction.commit() - android

I have an activity where I dynamically replace fragments:
private void goToFragment(Fragment newFragment, String tag) {
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.fragment_container, newFragment, tag);
ft.addToBackStack(null);
ft.commit();
}
Now, I want to access the views inside the fragment so I can put data (that I have stored in my activity) into them, immediately after calling goToFragment.
The problem is, the fragment's onCreateView isn't called before the fragment is rendered completely, at least to my understanding.
I know overriding the onAttach(Activity activity) in the fragment is one way to go about it, but then I have to cast it specifically to my activity - and I just want to avoid that because I consider it bad practice for the fragment to be dependent on a specific activity.
As far as I can see, Fragment doesn't have any listeners (as a subject) implemented.
So I figure I have to make my own listener (Using the Observer Pattern to make the fragment a subject and the activity an observer), and then calling it whenever the onCreateView or onAttach is done, and then finally calling back to the fragment with the data that needs to be set. However, I need to do this for several fragments so I would have to make a listener for each fragment, which I again think is bad.
Is there any better/easier way to do this?

FragmentTransaction isn't applied instantly after calling commit(). You may force update manually:
...
mFragmentManager.executePendingTransactions();
AFAIK event callbacks' purpose is custom communication with Fragment beyond it's usual lifecycle.

The correct way to do it would be to define an interface for Activity classes wishing to display your Fragment should implement. That way, on onAttach you don't cast to a specific Activity but to your interface.
See for instance: http://developer.android.com/guide/topics/fundamentals/fragments.html#EventCallbacks

You should use onActivityCreated to set the values.
Set references in onCreateView and then set values to them in onActivityCreated.

Related

How to clear backStack of support FragmentManager?

Is there any way, how to clear backStack of support FragmentManager without calling onCreateView() in stored fragments?
I understand fragment lyfe cycle and calling onDestroyView() and onCreateView() when it is popped.
http://developer.android.com/guide/components/fragments.html#Creating
Also I know how to pop all fragments from backstack with
mFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
or
for(int i = 0; i < mFragmentManager.getBackStackEntryCount(); ++i) {
mFragmentManager.popBackStack();
}
but both ways are calling onCreateView() and other lyfe cycle methods until to onDestroyView() and onDestroy().
But is there any way, how to clear this backstack with calling only from onDestroyView() and not from onCreateView() (inside of fragments)?
Or is there any way how to do replace transaction with clearing previous fragments?
For example, I want clear backstact before I do transaction:
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
fragmentManager.beginTransaction().replace(R.id.content, fragment).commit();
I haven't found a simple solution to this issue. I'm almost certain there is no feature of FragmentManager or Fragment that allows you to control which lifecycle method are called when a fragment is popped from the stack. I'll outline two possible approaches. Each has some undesirable aspects.
The first approach assumes the fragments you are popping out of the backstack are children of an activity. If they are children of a fragment, the method still applies, just a different type of parent object.
Add a boolean member mClearingBackStack to the activity (or parent fragment) with a getter method. Set the boolean only when you are starting a complete clear of the backstack. In the fragment lifecycle methods where you want to disable processing, get the flag and modify the processing accordingly. For onCreateView() through onDestroyView(), the fragment will be attached and the host activity available with getActivity(). Cast it to whatever your activity class is to use the flag's getter method.
Because popBackStack() is asynchronous, clearing the flag must be done only after the stack unwinding completes. I haven't tried it, but I think posting a Runnable after calling popBackStack() to clear the flag should work. Because the Runnable needs to go at the end of the queue, View.post() must be used instead of Activity.runOnUiThread(). An alternative is to call executePendingTransactions() to wait for the stack unwinding to complete.
The second approach is cleaner, if your design can accommodate it. Create a place-holder fragment that is a child of your activity and parent to all your other fragments. For all the fragment transactions you have now, use the new fragment's FragmentManager, obtained using getChildFragmentManager(). When you want to clear all those transactions, instead of popping the child fragment manager's stack, remove or replace the parent fragment in the activity's fragment manager. When the parent fragment is removed, all of its children are destroyed and go through the teardown steps, onDestroyView(), onDestroy(), etc. but not all the steps that would occur if its backstack were unwound. This approach is much simpler and more maintainable than the first, but requires you to rework your fragment hierarchy. An additional problem with this approach is that you must add some code to handle the Back action with a fragment hierarchy. The problem and various solutions are described here.

How to close fragment correctly?

Now I have a fragment to do something will last long time. In some case , the activity will call onSaveInstanceState when fragment is still running. After fragment do all the things , I want to close it. Here will throw an exception Can not perform this action after onSaveInstanceState.
I know what it means, but I really need to close the fragment after something has been done. So if I use commitAllowingStateLoss to force fragment to close , after activity recreate, the close state won't be recreated, the UI will be broken.
So how should I close the fragment correctly ?
I don't know any safe way to automatically close fragment, and it seems like a bad idea (why would you depend on the system for this?). You will have to time it correctly in your app. I can give you code suggestion to do it. Since there is no close method, you use remove method instead, or popBackStack. Since you did not post any code, I am suggesting a generic way to do this.
Code suggestion using remove:
Fragment fragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAGMENT);
...
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
transaction.remove(fragment);
transaction.commit();
Notes:
TAG_FRAGMENT is the ID for the fragment in the layout.
findFragmentByTag() is one way, another is new myFragment();
If you need to time when the Fragment is closed, use onDetach() override method of Fragment.

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!

Android how to send data to a running fragment

is there a way to send some data from an activity to a running fragment?
In my app I'm adding a second fragment over another fragment. I intentionally use the add method instead of the replace method. So now I want to hide my second fragment with
fragmentManager.popBackStack();
and my first fragment reappears. After hiding the second fragment I want to send some data from my activity to the still running frist fragment.
Any idea how to do this? It doesn't work with bundles (put extra), because I don't rebuild the fragment, I just hide the second one!
one simple solution is:
MyFragment oldFragment = (MyFragment) fragmentManager.findFragmentById(R.id.fragment_place);
fragmentManager.popBackStackImmediate();
MyFragment newFragment = (MyFragment)fragmentManager.findFragmentById(R.id.fragment_place);
newFragment.postData(...);
You can use an EventBus library like this one, it's easy to use and very convenient.
You can use tags on the Fragments when you create them to call them when needed.
getFragmentManager().beginTransaction()
.add(R.id.content, new SomeFragment(), SomeFragment.class.getSimpleName())
.commit();
So above I use the simple name of the class to tag the fragment when I create and add it to the activity.
SomeFragment fragment = (SomeFragment) getFragmentManager().findFragmentByTag(SomeFragment.class.getSimpleName());
And I can call it back when I need it and know it is being displayed like above, now I can call send it data like normal by calling a public method in the custom fragment and passing it the data as a param.

How to destroy a Fragment that is on Backstack?

I am trying to implement a method where I want to return to the previous fragment and destroy the current one. However, when I add the fragment to the Backstack it doesn't get destroyed anymore afterwards.
Is there any way to destroy it? Or maybe to return to the previous fragment without using the Backstack?
Edit:
I want to use the backwards navigation as well.
Firstly if you are using the BackStack, it is not typical to need to specifically manually remove Fragments, which suggests you might want to have another think about your design.
That said, to specifically manually remove a Fragment, Override onBackPressed in your Activity which is showing the Fragments, manually remove the Fragment there.
To make it easy to determine which Fragment is currently showing, you can give it a Tag when you show it. For example:
fragTrans.replace(android.R.id.content, myFragment, "MY_FRAGMENT_X");
Then in the onBackPressed function of your Activity
#Override
public void onBackPressed()
{
FragmentManager fragMan = getFragmentManager();
// Check if that Fragment is currently visible
MyFragment myFragment = (MyFragment)fragMan.findFragmentByTag("MY_FRAGMENT_X");
boolean myFragXwasVisible = myFragment.isVisible();
// Let the Activity pop the BackStack as normal
super.onBackPressed();
// If it was your particular Fragment that was visible...
if (myFragXwasVisible)
{
FragmentTransaction trans = fragMan.beginTransaction();
trans.remove(myFragment).commit();
}
}
Note: When it comes to specifically destroying your Fragment object, that is what Java's garbage collection is for. You don't need to worry about that yourself, Java will take care of destroying it when it needs to. That's the whole point.

Categories

Resources