I am having issues with nested/child fragments. My use case is: I have Frag A as parent fragment and FragChild1, FragChild2, FragChild3 as child fragments to be displayed inside Frag A. Now on back press from FragChild3 it should work like :
FragChild3 -> FragChild2 -> FragChild1 -> FragA(ParentFrag).
The code I used to add child fragments are-
for ChildFrag1-
Fragment mChildFragment1 = new ChildFragment1();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.replace(R.id.container_view, mChildFragment1);
transaction.addToBackStack("FragChild1");
transaction.commit();
for ChildFrag2-
Fragment mChildFragment2 = new ChildFragment2();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.replace(R.id.container_view, mChildFragment2);
transaction.addToBackStack("FragChild2");
transaction.commit();
I have searched through StackOverflow for relevant answers but haven't yet found any proper answer/way of managing backstack for child fragments.
Your code seems good. Just override onbackpressed method in activity that contains parent fragment and put given code in it.
if (parentfragment.getChildFragmentManager().getBackStackEntryCount() > 1) {
parentfragment.getChildFragmentManager().popBackStackImmediate();
} else {
super.onBackPressed();
}
Now this behavior can be implemented with OnBackPressedDispatcher without overriding onBackPressed in an Activity.
In your parent fragment (where your fragment container is located) add this code in onAttach method:
override fun onAttach(context: Context) {
super.onAttach(context)
val backCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// Remove all fragments from the childFragmentManager,
// but exclude the first added child fragment.
// This child fragment will be deleted with its parent.
if (childFragmentManager.backStackEntryCount > 1) {
childFragmentManager.popBackStack()
return
}
// Delete parent fragment
parentFragmentManager.popBackStack()
}
}
requireActivity().onBackPressedDispatcher.addCallback(this, backCallback)
}
Than add fragments in your container like this:
childFragmentManager.commit {
replace(R.id.fragmentContainerRoot, fragment)
addToBackStack(null)
}
Pass the tag into the replace method. TAG can as simple as the fragments name (String). Change your code like this:
transaction.replace(R.id.container_view, mChildFragment1, "FragChild1");
transaction.addToBackStack("FragChild1");
transaction.commit();
Try This,
Fragment fragment= new ChildFragment1();
getChildFragmentManager().beginTransaction().replace(R.id.contentView, fragment).addToBackStack(fragment.getClass().getName()).commitAllowingStateLoss();
Related
My application has a Fragment inside its Activity. I would like to programmatically replace the fragment by another one from the current fragment itself.
For example, if I click on a button inside the fragment, the fragment should be replaced with another one, but the activity should remain the same.
Is it possible? If so, how to do it?
It's actually easy to call the activity to replace the fragment.
You need to cast getActivity():
((MyActivity) getActivity())
Then you can call methods from MyActivity, for example:
((MyActivity) getActivity()).replaceFragments(Object... params);
Of course, this assumes you have a replaceFragments() method in your activity that handles the fragment replace process.
Edit: #ismailarilik added the possible code of replaceFragments in this code with the first comment below which was written by #silva96:
The code of replaceFragments could be:
public void replaceFragments(Class fragmentClass) {
Fragment fragment = null;
try {
fragment = (Fragment) fragmentClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
// Insert the fragment by replacing any existing fragment
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.flContent, fragment)
.commit();
}
from the official docs:
// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
In this example, newFragment replaces whatever fragment (if any) is currently in the layout container identified by the R.id.fragment_container ID. By calling addToBackStack(), the replaced fragment is saved to the back stack so the user can reverse the transaction and bring back the previous fragment by pressing the Back button.
The behavior you have described is exactly what fragments are designed to do. Please go through the official guide for a thorough understanding of fragments which will clear up all your questions.
http://developer.android.com/guide/components/fragments.html
Please note that fragment should NOT directly replace itself or any other fragments. Fragments should be separate entities. What fragment should do is to notify its parent activity that some event has happened. But it is, again, NOT a fragment job to decide what to do with that! It should be activity to decide to i.e. replace the fragment on phone, but to i.e. add another to existing one on tablets. So you are basically doing something wrong by design.
And, as others already mentioned, your activity should use FragmentManager ("native" or from compatibility library) to do the job (like replace() or add() or remove()):
http://developer.android.com/guide/components/fragments.html
Just as Marcin said, you shouldn't have a fragment start another fragment or activity. A better way to handle this situation is by creating a callback implementation for the main activity to handle requests such as start a new fragment. Here is a great example in the android developer guide.
There is a way which works; Just (in the fragment) do the following:
getFragmentManager().beginTransaction()
.add(R.id. container_of_this_frag, new MyNewFragment())
.remove(this)
.commit();
When using nested fragments, we don't want every inner fragment replacement goes to the outer most activity. A mechanism allowing a fragment to notify its parent that it wants to change to another fragment can be useful.
Here is my code in Kotlin, I think it is easy to translate into java.
interface FragmentNavigator {
fun navigateTo(fragment: Fragment)
}
class NavigableFragment: Fragment() {
var navigator: FragmentNavigator? = null
override fun onDetach() {
super.onDetach()
navigator = null
}
}
Inner fragments need to extend NavigableFragment, and use following code to change itself to another fragment.
navigator?.navigateTo(anotherFragment)
Outer activities or fragments need to implement FragmentNavigator, and override navigateTo.
override fun navigateTo(fragment: Fragment) {
supportFragmentManager.beginTransaction().replace(view_id, fragment).commit()
}
//Use childFragmentManager instead of supportFragmentManager a fragment
Finally in outer activities or fragments, override onAttachFragment
override fun onAttachFragment(fragment: Fragment?) {
super.onAttachFragment(fragment)
if(fragment is NavigableFragment) {
fragment.navigator = this
}
}
This worked for me:
getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container,
new MenuFragment()).commit();
For Kotlin.
(activity as YourActivityLauncherFragment)
.supportFragmentManager.beginTransaction()
.replace(R.id.yourFragmentContainer, YourFragmentName()).setReorderingAllowed(true)
.commit()
My app contains two tabs, inside two tabs every tab has 4-5 nested fragments, inside nested fragments addtobackstack is not working?
How can I add nested fragments to backstack because when I click back button inside nested fragments my app is closing means it's calling super.onBackKeyPressed method.
Add this code in your activity
public void onBackPressed() {
FragmentManager fm = getSupportFragmentManager();
if (fm.getFragments() != null) {
for (Fragment frag : fm.getFragments()) {
if (frag.isVisible()) {
FragmentManager chilFrag = frag.getChildFragmentManager();
if (chilFrag.getBackStackEntryCount() > 0) {
chilFrag.popBackStack();
return;
}
}
}
}
super.onBackPressed();
}
You should use ChildFragmentManager
This is fragment replace function.
fun replace(fragment: Fragment){
childFragmentManager
.beginTransaction()
.replace(R.id.fragmentContainer, fragment)
.commit()
}
And override onBackPressed method in most parent fragment.
override fun onBackPressed() {
val pop = childFragmentManager.popBackStackImmediate()
if (!pop){
super.onBackPressed()
}
}
I wrote with kotlin i hope you can understand.If you need more info, please ask to me.
Use ChildFragmentManager and check if active tab has BackStackEntry, if so then pop backstack else call super.onBackKeyPressed
complete description on this answer:
https://stackoverflow.com/a/37961649/4832356
I am using drawer menu in my app.I select one option from menu and open fragment and from that fragment call an Activity.Since here it is working fine but when I press back button(OnbackPress) then app is crashed.
bellow is the error.
"Unable start activity...ClassCastException...cannot be cast to Home_Tab"
This is MainActivity code.
if (savedInstanceState == null) {
homefragment = Home_tab()
fragmentTransaction = fragmentManager!!.beginTransaction()
fragmentTransaction!!.replace(R.id.frame, homefragment)
fragmentTransaction!!.addToBackStack(null)
fragmentTransaction!!.commit()
} else {
homefragment = supportFragmentManager.fragments[0] as Home_tab //Crash at this line
}
Code from where backPress Called.
override fun onBackPressed() {
super.onBackPressed()
finish()
}
//Add a check like this before casting.
//It is a smart cast and you can directly use the result.
Fragment fragmentZero = supportFragmentManager.fragments[0]
if (fragmentZero is Home_tab) {
//Casting is done, you can directly use fragment here
homefragment = fragmentZero
}
Well based on your code you add the fragment to the fragmentmanager (regular one) but try to get it back from the supportfragmentmanager. Those are two different classes and your fragment can only extend one
You're are mixin fragment manager et support fragment manager, i'll go with suppport one since this is the right way to do it. To get current display fragment added with a container ID use findFragmentById
if (savedInstanceState == null) {
homefragment = Home_tab()
supportFragmentManager?.let{
fragmentTransaction = it.beginTransaction()
fragmentTransaction.replace(R.id.frame, homefragment)
fragmentTransaction.addToBackStack(null)
fragmentTransaction.commit()
}
} else {
homefragment = supportFragmentManager.findFragmentById(R.id.frame) as Home_tab
}
I know how to add a Fragment to the backstack but how do I know, when the user presses the back Button, which fragment I left and which I went to? I need to do a certain action depending on this so I need to know from and to which fragment I am going. Specifically I need to know which fragment I left so if it is a certain fragment, I can remove a button.
Override onBackPressed in Activity:
#Override
public void onBackPressed(){
FragmentManager fm = getSupportFragmentManager();
Fragment f = fm.findFragmentById(R.id.content_frame); // get the fragment that is currently loaded in placeholder
Object tag = f.getTag();
// do handling with help of tag here
// call super method
super.onBackPressed();
}
You can add the Fragment like this :
ArticleMain articalemain = new ArticleMain();
getFragmentManager().beginTransaction().add(R.id.fragment_Top, articalemain, "MY_FRAGMENT").commit();
While Removing the Fragments do like this :
ArticleMain myFragment = (ArticleMain) getFragmentManager()
.findFragmentByTag("MY_FRAGMENT");
if (myFragment.isVisible()) {
// add your code here
myFragment.getFragmentManager().beginTransaction()
.remove(myFragment).commit();
}
My application has a Fragment inside its Activity. I would like to programmatically replace the fragment by another one from the current fragment itself.
For example, if I click on a button inside the fragment, the fragment should be replaced with another one, but the activity should remain the same.
Is it possible? If so, how to do it?
It's actually easy to call the activity to replace the fragment.
You need to cast getActivity():
((MyActivity) getActivity())
Then you can call methods from MyActivity, for example:
((MyActivity) getActivity()).replaceFragments(Object... params);
Of course, this assumes you have a replaceFragments() method in your activity that handles the fragment replace process.
Edit: #ismailarilik added the possible code of replaceFragments in this code with the first comment below which was written by #silva96:
The code of replaceFragments could be:
public void replaceFragments(Class fragmentClass) {
Fragment fragment = null;
try {
fragment = (Fragment) fragmentClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
// Insert the fragment by replacing any existing fragment
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.flContent, fragment)
.commit();
}
from the official docs:
// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
In this example, newFragment replaces whatever fragment (if any) is currently in the layout container identified by the R.id.fragment_container ID. By calling addToBackStack(), the replaced fragment is saved to the back stack so the user can reverse the transaction and bring back the previous fragment by pressing the Back button.
The behavior you have described is exactly what fragments are designed to do. Please go through the official guide for a thorough understanding of fragments which will clear up all your questions.
http://developer.android.com/guide/components/fragments.html
Please note that fragment should NOT directly replace itself or any other fragments. Fragments should be separate entities. What fragment should do is to notify its parent activity that some event has happened. But it is, again, NOT a fragment job to decide what to do with that! It should be activity to decide to i.e. replace the fragment on phone, but to i.e. add another to existing one on tablets. So you are basically doing something wrong by design.
And, as others already mentioned, your activity should use FragmentManager ("native" or from compatibility library) to do the job (like replace() or add() or remove()):
http://developer.android.com/guide/components/fragments.html
Just as Marcin said, you shouldn't have a fragment start another fragment or activity. A better way to handle this situation is by creating a callback implementation for the main activity to handle requests such as start a new fragment. Here is a great example in the android developer guide.
There is a way which works; Just (in the fragment) do the following:
getFragmentManager().beginTransaction()
.add(R.id. container_of_this_frag, new MyNewFragment())
.remove(this)
.commit();
When using nested fragments, we don't want every inner fragment replacement goes to the outer most activity. A mechanism allowing a fragment to notify its parent that it wants to change to another fragment can be useful.
Here is my code in Kotlin, I think it is easy to translate into java.
interface FragmentNavigator {
fun navigateTo(fragment: Fragment)
}
class NavigableFragment: Fragment() {
var navigator: FragmentNavigator? = null
override fun onDetach() {
super.onDetach()
navigator = null
}
}
Inner fragments need to extend NavigableFragment, and use following code to change itself to another fragment.
navigator?.navigateTo(anotherFragment)
Outer activities or fragments need to implement FragmentNavigator, and override navigateTo.
override fun navigateTo(fragment: Fragment) {
supportFragmentManager.beginTransaction().replace(view_id, fragment).commit()
}
//Use childFragmentManager instead of supportFragmentManager a fragment
Finally in outer activities or fragments, override onAttachFragment
override fun onAttachFragment(fragment: Fragment?) {
super.onAttachFragment(fragment)
if(fragment is NavigableFragment) {
fragment.navigator = this
}
}
This worked for me:
getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container,
new MenuFragment()).commit();
For Kotlin.
(activity as YourActivityLauncherFragment)
.supportFragmentManager.beginTransaction()
.replace(R.id.yourFragmentContainer, YourFragmentName()).setReorderingAllowed(true)
.commit()