I understand how to navigate between fragments using Jetpack's Navigation Component, but I haven't been able to find is how to navigate from one child fragment to another child fragment.
The following is what I've done so far:
https://pastebin.com/dNQ0Ep4S the code example is pretty big so sum it up, I'm trying to do is" A>B and Ba>Bb". Ba is a fragment inside of fragment B. I'm not sure how to set up the nav graph for something like that
The navigation works until the home fragment. The bot nav just doesn't seem to work. It always displays the pending layout. Another thing I tried was setting the nav graph of the home to the same one used for the login and adding the pending and history fragments to the nav file without any actions. But the home loads the login, the tabs work, but they're replacing the home fragment instead of being placed in the home nav host.
Update
I managed to find the problem, but I'm stuck trying to find a solution.
So B seems to be getting the navhost for A when I setup the botnavview to the navcontroller. So calling NavController navController = NavHostFragment.findNavController(this); from B returns the navhost of A. I'm at a lost here. If I do this NavController navController2 = Navigation.findNavController(currentActivity, R.id.homeNavHostFragmentContainer); (homeNavHostFragmentContainer being the navhostfor B) still returns the navhost for A. For some reason, I can't get a reference to B's navhost.
Usually we use: NavigationUI with jetpack, i recommend this, https://developer.android.com/guide/navigation with nav_graph.xml with NavigationDirections such as:
val direction = FirstFragmentDirections.actionSecondFragment()
Navigation.findNavController(requireView()).navigate(direction)
Or you can use non-jetpack way, i do not recommend, but depending on your UI you may need it:
fun displayChildFragment(frameId: Int, fragment: Fragment) {
requireActivity().supportFragmentManager?.let {
val transaction = it.beginTransaction()
//transaction.setTransition(TRANSIT_FRAGMENT_OPEN)
transaction.replace(frameId, fragment).commit()
}
}
My problem was that I misunderstood how NavHostFragment.findNavController() functions. Fragment B is not a navhost is just a regular Fragment. The navhost for B is a child of B. The argument for findNavController() has to be a NavHostFragment not a regular Fragment. So by passing, this (being B), it wasn't getting a NavFragment, just a regular Fragment. I thought the method would have extracted the navhost from whatever fragment was passed. Guess the documentation caused a bit of confusion. To get the correct navhost, I had to get the navhost from the fragment manager like so getChildFragmentManager().findFragmentById(navHostResId) (in my case, navHostResId was the id of the fragment container in B). Then everything worked beautifully.
Related
First of all, I'm pretty new to Android development.
I'm building an simple Android app with 1 Activity which contains 3 fragments.
Lets name the fragments A, B and C. Each fragment contains some input fields and a "next" button. The last fragment contains a "finish" button which sends the data (collected in the fragments) to an API and navigates to fragment A again to start over again.
In fragment A and B, i navigate to the next fragment by calling this function:
private void replaceFragment(Fragment someFragment) {
FragmentTransaction transaction = getParentFragmentManager().beginTransaction();
transaction.replace(R.id.nav_host_fragment_content_main, someFragment);
transaction.addToBackStack(null);
transaction.commit();
}
This works fine so far. In fragment C i post the collected data to the API and call this code afterwards:
FragmentManager fm = getParentFragmentManager();
fm.popBackStackImmediate(0, FragmentManager.POP_BACK_STACK_INCLUSIVE);
This works perfect but only once. After i'm "redirected" from fragment C to fragment A and go again via B to C and click Finish in fragment C, the mentioned code is triggered but I'm not redirected to A anymore.
When i kill the app and restart the app, it works once again. What I'm doing wrong?
Update
During debugging i can see that the BackStack is a list with BackStackEntries. This list always contains 2 entries when I'm in frament C and i thought fm.popBackStackImmediate(0,... "redirects" to entry 0 of the BackStack. Now i notice that each BackStackEntry has a property mIndex which is increasing all the time. Probably is the id parameter in the popBackStackImmediate not the position in the BackStack but refers to the BackStackItem its mIndex.
Is this true and if yes, how can i get the ID of the correct back stack item in this case?
It seems that the id in fm.popBackStackImmediate(0, FragmentManager.POP_BACK_STACK_INCLUSIVE); is not the position of the BackStackEntry in the BackStack but the id of the BackStackEntry. This id is increasing never reused so its continuously increasing.
The BackStackEntry of fragment A is always on position 0 in my case so i can simply get its id like:
Integer $homeFragmentBackStackId = fm.getBackStackEntryAt(0).getId();
And than do:
fm.popBackStackImmediate($homeFragmentBacStackId,FragmentManager.POP_BACK_STACK_INCLUSIVE);
I have an activity called PeopleActivity, with a layout xml of activity_people.xml.
This activity is using the Android Studio Navigation Drawer Activity template.
I have a list view in the content_people.xml file named people_list. When I attempt to associate this control in the back end, it is unable to find the id. I am attempting it as:
peopleList = (ListView) findViewById(R.id.people_list);
Any insight would be greatly appreciated.
EDIT: Did an update to the code section to reflect the correct control referenced.
you should declare your listview like this:
peopleSearch = (ListView) findViewById(R.id.people_list);
not as EditText
I found the answer. You cannot use capital letters in the naming of your controls. Once I fixed this, everything worked fine.
I'm fairly new to android studio, any help would be appreciated.
I have set up a bottom navigation bar programatically in my MainActivity - what's the best way to set this up with other fragments. I have three fragments, one for each tab in the navigation bar and other fragments which can be opened when buttons are pressed from the navigation bar fragments.
Where do I set up these other fragments? in the same activity that connects the fragments that are connected to the navigation bar or in a different activity.
How do I save the current state of the displayed fragment so that when I move to a different tab and then move back it will be in the same state as when I left it?
My question is, where do i set up these other fragments? in the same activity that connects the fragments that are connected to the navigation bar or in a different activity.
It's really up to you and how you want to display the fragments. You can display them in the same activity or open another activity. However bear in mind that if you open another activity, you will lose the navigation bar of the previous activity (an activity always uses the whole screen)
What does FragmentManager and FragmentTransaction exactly do?
How do I save the current state of the displayed fragment so that when
I move to a different tab and then move back it will be in the same
state as when i left it?
Read about the fragment lifecycle at https://developer.android.com/guide/components/fragments.html#Lifecycle
Specifically, you want to save your state in onSaveInstanceState, and the stuff you save will be sent back to you when the fragment is recreated in onCreate
I'd like to expand on what #rupps said, because I feel like the part about what do FragmentManager/Transaction do is not approached from where you are expecting.
I assume you're using a BottomNavigationView.
Regardless of the (important) lifecycle of Fragments, you have to understand that a Fragment is always attached to an activity (note: this is not true, but let's not talk about headless fragments for now).
The approach you can take is that the Activity layout looks like this: (in pseudo code)
<RelativeLayout width=match_parent height=match_parent>
<FrameLayout
id="#+id/your_fragment_container"
width=match_parent
height=match_parent
layout_above="#+id/navbar" />
<BottomNavigationView
id="#id/navbar"
width=match_parent
height=wrap_content
align_parent_bottom=true />
</RelativeLayout>
This way the BottomNavBar will always be present at the bottom of your layouts.
Now you have to deal with putting the fragments there… Let's say that you need to attach a Listener to that bar, and when you receive a callback that a new menu item has been selected… you can proceed to change the fragment (you will always get one event upon startup or you can force it during onCreate I suppose).
You will literally add a switch/if statement to the onNavigationItemSelected(MenuItem item) method.
and call addFragment(TAG); depending which case it is.
Pseudo-Code for you to get the idea:
private void addFragment(final String tag) {
final Fragment existing = getSupportFragmentManager().findFragmentByTag(tag);
if (existing == null) {
final Fragment newInstance = getNewFragmentInstanceWithTag(tag);
getSupportFragmentManager()
.beginTransaction()
.replace(getFragmentContainerLayout(), newInstance, tag)
.commit();
}
}
You'll also need to provide:
private int getFragmentContainerLayout() {
return R.id.your_fragment_container;
}
and…
public static final String TAB1_TAG = "TAB1_TAG";
public static final String TAB2_TAG = "TAB2_TAG";
public static final String TAB3_TAG = "TAB3_TAG";
protected Fragment getNewFragmentInstanceWithTag(String tag) {
switch (tag) {
case TAB1_TAG:
return Tab1Fragment.newInstance();
case TAB2_TAG:
return Tab2Fragment.newInstance();
case TAB3_TAG:
return Tab3Fragment.newInstance();
default:
return null;
}
}
So what the frog is the FragmentManager/Transaction?
Think of the Manager as a singleton object (one per app) that keeps a reference to your Fragments and can retrieve them for you (if they existed before). It handles Transactions (add/remove/hide/show, etc.) so you can later roll back them (say you add a fragment in a transaction, if you also addToBackStack() then you can simply tell the Manager: pop the last transaction, effectively rolling it back.
It's a monster. It had bugs for over 9000 years and it's not very intuitive; but once you get used to it, you just "use it".
I am in a peculiar situtation in my app.
When i app first loads there is a custom listview which is populated with data from the server.I am also using a class which contains different fields for the string data from the server.
When i click an item on the custom listview,the object of the corresponding class is passed onto the next fragment.
That is the current fragment is replaced with a new fragment and the object is passed with bundle.
Now a new listview loads with different tasks.On clicking a task a new fragment with a camera is loaded.
After taking the image and uploading to server, the status in the JSON changes to "COMPLETED".But now when i press back the old listview is shown.
Is there a way to populate the listview on back pressed with new data?
The issue is that I am passing an object right from the first fragment.
Now i need a new object on back pressed,how to pass the new object on back pressed?
When Fragment 2 gets the data, it should pass it along at some point before Fragment 1 is woken.
There are almost a half dozen ways to pass data, and the best way depends on a number of factors like who should own the lifecycle of the data, data pull vs push, dependency between fragments, do multiple components need updating, etc.
I'm just going to advise to simply cache the data on the activity until you learn more about the different methods.
//Fragment 2 puts data to activity
((MyActivity) getActivity).mListViewData = listViewData;
Then the next part of the question is how does fragment 1 get the data. Fragment 1 is hibernating on the backstack. When it wakes up it will call the onViewCreated() method (because it's previous view was destroyed before being placed on the backstack).
In that method, we check if there's new data waiting for Fragment 1.
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
MyDataType listViewData = ((MyActivity) getActivity).mListViewData;
if(listViewData != null){
//setData is your own function for replacing the adapters
//data backing
listView.getAdapter().setData(listViewData);
}else{
listView.getAdapter().setData(...defaultData);
}
listView.getAdapter.notifyDataChanged();
}
Override the onBackPressed in the Activity that manages the Fragments. In it you can check if the fragment is visible or not (the one from which an action should be performed if the back is pressed) and then execute your action.
#Override
public void onBackPressed(){
Fragment myFragment = getFragmentManager().findFragmentByTag("MY_FRAGMENT");
if (myFragment.isVisible()) {
String json = myFragment.getJsonData(); //update it locally
if(isUpdated){
Fragment listFragment = getFragmentManager().findFragmentByTag("MY_LIST_FRAGMENT");
listFragment.updateListView(json); //Add this method on your fragment
}
}
super.onBackPressed();
}
Obs.: To use the .findFragmentByTag() you should add tags once you're making the transaction like so:
fragTrans.replace(android.R.id.content, myFragment, "MY_FRAGMENT");
If, for any reason the listFragment has been cleaned from memory, you would have to reload the data anyway so just download the new data again.
To update the ListView please see: How to refresh Android listview? . Note thought that you will need to will need to send a new data set to the list view (which you can do inside the updateListView() method)
I am new to Andoid development and I have created a snippet for replacing fragments programetically.
I followed the guide on android developers.
I have create a method named selectFrag and fired it on button click:
public void selectFrag(View view)
{
Fragment fr;
if(view == findViewById(R.id.showsecond)) {
fr = new secondfragment();
} else {
fr = new firstfragment();
}
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment_place,fr);
ft.addToBackStack(null);
ft.commit();
}
Code works perfectly and I understand everything except addToBackStack(null).
I experimented and understood that this method is for adding the fragment to the back button's stack, so that if we click back button, it do not leave the screen and show the previous work.
But, I do not understand what null shows here. I searched on web but I only knew that it is a TAG and we could use something like this.
So, my question is very simple :What is meant by null here? or What does null do?
(sorry for my bad English.)
From documentation, it is pretty clear:
public abstract FragmentTransaction addToBackStack (String name)
Add this transaction to the back stack. This means that the transaction will be remembered after it is committed, and will reverse its operation when later popped off the stack.
Parameters
name // An optional name for this back stack state, or null.
So your parameter is optional and represents the name of the fragment.
If you just want to add this transaction to the back stack and don't need to access it later then you can put null as the name.
In this context, null in plain English means "I don't need a name for this fragment". That is why it says the name is optional. If you do put a name you can use that name later. If you put a null that just means "add this fragment to the back stack and I don't need it anymore".
The use of the name is to identify that specific fragment. This can be useful for example if you want to obtain that fragment from the FragmentManager:
addToBackStack (FRAGMENT_NAME);
getFragmentMangager().findFragmentByTag(FRAGMENT_NAME);
Just want to clarify, the 'name' used in addToBackStack(name) can't be used for retrieving the fragment by calling fragmentManager.findFragmentByTag(tag). The 'tag' is different from the 'name'.
The 'tag' used in add/replace(id, fragment, tag) is used to retrieve the fragment by calling fragmentManager.findFragmentByTag(tag).
However, the 'name' used in addToBackStatck(name) is used to control to which fragment you want to pop the fragment back stack by calling popBackStatck/Immediate(name, flags). So if I have a fragment stack with named fragments: A, B, C and D with A at the bottom. When you call popBackStack(B, XXX_EXCLUSIVE), then your fragment back stack will be like: A and B after the call. Without the name, you can't do that.
I think you should not use .addToBackStack(null);
if you are trying to popthe Backstack chances are that it might throw
java.lang.IllegalStateException as it wont be having valid reference to Popout the last item which is added. This needs to be checked... i am not 100% sure.