Get fragment from NavController - android

I have one NavController (navOrdersController) with 2 fragments inside. My current fragment is OrderDetailFragment and I want access to first Fragment (OrdersFragment) to update data in MainActivity. How can I do this?
I tried findFragmentByTag and findFragmentByID but always return null.
I got access OrderDetailFragment with:
this.supportFragmentManager.fragments[position].findNavController().currentDestination
But I need previous fragment. Thank you very much

Can't say the code snippet (I've added below) is the best solution, but it worked for me to get the recent/current fragment added in NavHostFragment used in Android Navigation Component's implementations
OR
You can even override onActivityResult of the you activity with your fragment's, all you need is to get the added Fragment and call it's onActivityResult, using this code snippet:
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
Fragment fragment = navHostFragment.getChildFragmentManager().getFragments().get(0);
fragment.onActivityResult(requestCode, resultCode, data);

If both the fragments have same parent activity, i.e. use getActivity() to get activity reference and use Activity.getSupportFragmentManager to find the fragment.
.....
Activity X -> Fragment A -> Fragment B
If fragment B is child of Fragment A, use getParentFragment. The fragment you are looking for would be this fragment.
Let me know if anything is not clear.
PS: Jetpack Navigation library does the same thing which we have been doing explicit. So, the previous logic of Activity-Fragment is still valid.
EDIT:
try out (parentFragment as NavHostFragment).childFragmentManager.findFragmentById()
I forgot last time that activity has declaration of NavHostFragment in its layout so there will always root parent fragment of type 'NavHostFrament'
EDIT:
I have found a work around for it
parentFragment.childFragmentManager.getFragment(Bundle().also { it.putInt("foobar",0) }, "foobar")

Related

How to get top Fragment in backstack from DialogFragment

I'm using navigation to navigate between fragments.
And I have a DialogFragment, it can called from from many fragment like this:
val dialog = FragmentDialog
dialog.show(childFragmentManager, "home_fragment")
And I want to know dialogfragment called by which fragment
I tried with FragmentManager.backStackEntryCount but it's seem doesn't work
Can I have a advice for this problem ???
There are multiple ways to do this ..
You can just Pass a key in Bundle to your DialogFragment to check which Fragment opened it ..
If your Dialog fragment a attached to a fragment in each case you can just use getParentFragment() . getParentFragment() will return the instance of that fragment. If its attached to activity then getParentFragment() will be null so watch out for this case also .
you can also use getTag() from where it was open byt passing different tags to transaction .

Android. How to avoid fragment be recreated when back from another fragment?

I'm using MVP in my project. I have an activity. Inside this activity I'm showing fragment. Fragment contains some data, edit text, buttons etc. From this fragment it is possible to navigate to another fragment. Here is my code for showing another fragment:
getParentFragmentManager()
.beginTransaction()
.replace(R.id.main_container, secondFragment)
.addToBackStack(null)
.commitAllowingStateLoss();
Next, when I try to go back from fragment 2 to fragment 1, my fragment one is re-created.The method onViewCreated() is called again with saveedInstanceState = null. Also in the onViewCreated() I call presenter.onCreate(savedInstanceState) and because of this, all requests that should be called when I first enter the fragment are re-called when I back to first fragment.
As far as I understand, when calling a .replace(container, fragment), I cannot avoid recreating the fragment view, but in this case, how can I save all my data in the presenter so that I do not re-execute all requests when returning to the fragment?
P.S. With .add() fragment is not recreated. But in my activity I have toolbar with title, and I don't need it to show in my second fragment. When I open my second fragment with .replace() toolbar is not showing, but when open with .add() toolbar is showing.
Use Fragment Result API :
For implemention this method set - setFragmentResultListener - in source fragment and also set - setResult() and finish() - in destination fragment
Note 1 : for having clean code i suggest you to use FragmentTransaction class in:
https://github.com/mahditavakoli1312/FragmentTransaction---mahdi-tavakoli
Note 2 : use Navigation for navigate betwin fragments and activities Instead tranastion

Get parent fragment from child when using Navigation Component

I need to transfer data from one fragment to another. Now the recommended way to do this is to use a shared ViewModel. To get the same instance available in both fragments, common owner is needed. As it can be their common Activity. But with this approach (In the case of Single Activity), the ViewModel instance will live throughout the entire application. In the classic use of fragments, you can specify ViewModelProvider (this) in the parent fragment, and ViewModelProvider (getParentFramgent ()) in the child. Thus, the scope of ViewModel is limited to the life of the parent fragment. The problem is that when using Navigation Component, getParentFramgent () will return NavHostFragment, not the parent fragment. What do I need to do?
Code samples:
Somewhere in navigation_graph.xml:
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/nav"
app:startDestination="#id/mainMenuFragment">
<fragment
android:id="#+id/mainMenuFragment"
android:name="com.mypackage.mainmenu.MainMenuFragment"
android:label="MainMenu"
tools:layout="#layout/fragment_main_menu">
<action
android:id="#+id/start_game_fragment"
app:destination="#id/gameNav" />
</fragment>
<navigation
android:id="#+id/gameNav"
app:startDestination="#id/gameFragment">
<fragment
android:id="#+id/gameFragment"
android:name="com.mypackage.game.GameFragment"
android:label="#string/app_name"
tools:layout="#layout/fragment_game"/>
</navigation>
</navigation>
Somewhere in MainMenuFragment:
override fun startGame(gameSession: GameSession) {
//This approach doesn't work
ViewModelProvider(this)[GameSessionViewModel::class.java].setGameSession(
gameSession
)
findNavController().navigate(R.id.start_game_fragment)
}
GameFragment:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
gameSessionViewModel =
ViewModelProvider(requireParentFragment())[GameSessionViewModel::class.java].apply {
val session = gameSession.value
)
}
}
EDIT:
I can use NavHostFragment(returned from getParentFragment()) as a common for all fragments, but then, as in the case of Activity, ViewModel.onCleared() will not be called when the real parent fragment finishes.
There's really no way to do this.
Here is a code snippet from androidx.navigation.fragment.FragmentNavigator:
public NavDestination navigate(#NonNull Destination destination, #Nullable Bundle args,
#Nullable NavOptions navOptions, #Nullable Navigator.Extras navigatorExtras) {
// ...
final FragmentTransaction ft = mFragmentManager.beginTransaction();
// ...
ft.replace(mContainerId, frag);
ft.setPrimaryNavigationFragment(frag);
// ...
}
Under the hood, the FragmentManager is used, which calls replace(). Therefore, the child fragment is not added, but is replaced with a new one, so it will not be in getParentFramgent().
I faced the same problem and after researching and experimenting, I found the solution.
You simply have to call this while scoping your ViewModel which will resolve to your fragment where you have created the navigation host fragment.
ViewModelProvider(getParentFragment().getParentFragment())
or more appropriately
ViewModelProvider(requireParentFragment().requireParentFragment())
(to avoid NPE)
This is because child fragment's parent is NavHostFragment and NavHostFragment's parent is ParentFragment.
I tested this and it's working fine for me
for those of you who are bothered with this problem, here is the answer that worked for me:
suppose that you go from fragment A to fragment B, and you want to use a shared ViewModel for fragment A and B, and in this case, A is a fragment and B is a dialogFragment.
you can initialize ViewModel in fragment A:
ViewModelProvider(this).get(AviewModel::class.java)
and in order to use it on fragment B, initialize the ViewModel in fragment B like this:
ViewModelProvider(requireParentFragment().childFragmentManager.fragments[0]).get(AviewModel::class.java)
TL;DR:
requireParentFragment() returns NavHostFragment which hosts all the navigation of the current navigation.
requireParentFragment().childFragmentManager returns FragmentManagerImpl which seems to be the container for all fragments.
requireParentFragment().childFragmentManager.fragments returns a mutable list of fragments that are in the stack, so you can iterate through the list to see the fragments that are living in stack.

FragmentManager popBackStack doesn't remove fragment

I'm implementing menu navigation using Fragments. So I begin with Home, and then users can navigate to diferent sections and details of each section.
When a user changes section, then I call pop on the fragmentmanager backstack until I reach Home, and then load the new section.
This is all working as expected. But I'm getting this problem:
load a section that calls setHasOptionsMenu(true) on onResume()
loads another section (old section it's suposed to get out of the stack). I see it OK. No menu is shown
leave the application (for example, go to Android Laucher activity) and then when I return, I see the correct section, but it's showing the Menu of the old Fragment.
I've iterated the backstack and printed each fragment, and there it's not the fragment with the menu.
I put a debug mark on the onResume() method (where the setHasOptionsMenu(true) is flagged) and it indeed enters here, so the Fragment it's still somewhere.
I want to know if I'm doing something wrong and how could I solve it, thx
Update:
I'm using this code to load new fragments
fm.beginTransaction()
.add(container, sectionFragment.getFragment())
.addToBackStack(sectionFragment.getFragmentName())
.commit();
And for remove:
private void clearStack(){
int count = fm.getBackStackEntryCount();
while(count > 1){
fm.popBackStack();
count--;
}
}
NOTE 1: I'm using add instead replace because I don't want to loose the state of my fragment when I navigate back from detail section. When I load another different section, then I call clearStack to pop the stack up to 1, and then loads new fragment. At the end, I'm calling executePendingTransactions() to finish to remove the fragments from the transaction.
NOTE 2: I'm seeing that it is entering on my fragment onDestroy() method, so it is suposed to be destroyed. But I don't know why it is getting called again when the Main activity resumes.
I found that the problem was not in the logic of adding and removing fragment of the stack.
The problem was that some of the fragment loaded another fragments inside of it (it had ViewPager component). Then I thought that when the fragment was removed then these fragments were removed too.
This is true ONLY if you use getChildFragmentManager() method. This method MUST be used when loading fragments inside other fragmets. If not, then the fragments are asociated with the fragments activity.
popBackStack will just revert your last FragmentTransaction.
If you use FragmentTransaction.add, popBackStack will just call FragmentTransacetion.remove.
But if you call FragmentTransaction.replace, popBackStack will call FragmentTransaction.remove and FragmentTransaction.add
For your "NOTE 1" :
FragmentTransaction.replace will not change your fragment state.
I found this question, because after calling
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
this code fragmentManager.getFragments().size() returns me the maximum number of fragments, which were in the stack. I checked every fragment on null. And I found that some fragment is null in my case. Maybe it will help someone)
If you are really looking to remove fragments at once then follow:
How to replace Fragments of different types?
Otherwise use replace transaction for fragments to smooth transitiona and hassel free approach, see https://stackoverflow.com/a/23013075/3176433
Also understand Fragment lifecycle,
http://developer.android.com/guide/components/fragments.html
I had a similar problem where the popBackStack() didn't remove my fragment.
However, I noticed that I called the wrong FragmentManager, where I had to call getSupportFragmentMananger() instead of getFragmentManager().
Maybe there is a <fragment> or <androidx.fragment.app.FragmentContainerView> in an activity with android:name="androidx.navigation.fragment.NavHostFragment", app:defaultNavHost="true" and app:navGraph="#navigation/nav_graph".
In this case navigation is held by nav_graph. If you don't want to use NavController and NavHostFragment, maybe you should remove navigation and clean <fragment> tag.

Communicating between fragments in Android

Based on the example from http://developer.android.com/training/basics/fragments/communicating.html I tried to reproduce the communication between two fragments which are sub fragments of a larger fragment.
In the example, AB activity contains A fragment and B fragment. But I am trying to achieve the same but in my case AB Fragment contains A fragment and B fragment.
The problem is the overridden method in the AB Fragment never gets called. Does this not work because the containing component is a Fragment and not a Activity like in the example? Am I missing out something here?
If you are referring to onClick() or some other onSomething() handler, then these always get called in the Activity class, not the fragment. So in the example you linked, the onArticleSelected() must remain in the Activity, even if you have nested fragments.
To pass info on to the fragment, you have a few options. One, you can keep a reference to the fragment within the activity. This might be lost if your activity recreates (settings event for example).
The second and better way would be to tag your fragments, and then use findFragmentByTag.
When you add your fragment (notice the parameter "my_fragment" which is the tag I gave to the fragment):
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, myFragment, "my_fragment").commit();
Or when you replace one fragment with another:
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, myFragment, "my_fragment").comit();
Then, when you want to do something in the fragment from within your onArticleSelected of the activity:
Fragment fragment = getSupportFragmentManger().findFragmentByTag("my_fragment");
if (fragment != null) {
fragment.articleSelected(articleId);
}
You can always use an Interface to communicate between fragments. It is the safest way to do so.

Categories

Resources