I've been following the docs from Navigation Architecture Component to understand how this new navigation system works.
To go/back from one screen to another you need a component which implements NavHost interface.
The NavHost is an empty view whereupon destinations are swapped in and
out as a user navigates through your app.
But, it seems that currently only Fragments implement NavHost
The Navigation Architecture Component’s default NavHost implementation is NavHostFragment.
So, my questions are:
Even if I have a very simple screen which can be implemented with an Activity, in order to work with this new navigation system, a Fragment needs to be hosted containing the actual view?
Will Activity implement NavHost interface in a near future?
--UPDATED--
Based on ianhanniballake's answer, I understand that every activity contains its own navigation graph. But if I want to go from one activity to another using the nav component (replacing "old" startActivity call), I can use activity destinations. What is activity destinations is not clear to me because the docs for migration don't go into any detail:
Separate Activities can then be linked by adding activity destinations to the navigation graph, replacing existing usages of startActivity() throughout the code base.
Is there any benefit on using ActivityNavigator instead of startActivity?
What is the proper way to go from activities when using the nav component?
The navigation graph only exists within a single activity. As per the Migrate to Navigation guide, <activity> destinations can be used to start an Activity from within the navigation graph, but once that second activity is started, it is totally separate from the original navigation graph (it could have its own graph or just be a simple activity).
You can add an Activity destination to your navigation graph via the visual editor (by hitting the + button and then selecting an activity in your project) or by manually adding the XML:
<activity
android:id="#+id/secondActivity"
android:name="com.example.SecondActivity" />
Then, you can navigate to that activity (i.e., start the activity) by using it just like any other destination:
Navigation.findNavController(view).navigate(R.id.secondActivity);
I managed to navigate from one activity to another without hosting a Fragment by using ActivityNavigator.
ActivityNavigator(this)
.createDestination()
.setIntent(Intent(this, SecondActivity::class.java))
.navigate(null, null)
I also managed to navigate from one activity to another without hosting a Fragment by using ActivityNavigator.
Kotlin:
val activityNavigator = ActivityNavigator( context!!)
activityNavigator.navigate(
activityNavigator.createDestination().setIntent(
Intent(
context!!,
SecondActivity::class.java
)
), null, null, null
)
Java:
ActivityNavigator activityNavigator = new ActivityNavigator(this);
activityNavigator.navigate(activityNavigator.createDestination().setIntent(new Intent(this, SecondActivity.class)), null, null, null);
nav_graph.xml
<fragment android:id="#+id/fragment"
android:name="com.codingwithmitch.navigationcomponentsexample.SampleFragment"
android:label="fragment_sample"
tools:layout="#layout/fragment_sample">
<action
android:id="#+id/action_confirmationFragment_to_secondActivity"
app:destination="#id/secondActivity" />
</fragment>
<activity
android:id="#+id/secondActivity"
android:name="com.codingwithmitch.navigationcomponentsexample.SecondActivity"
android:label="activity_second"
tools:layout="#layout/activity_second" />
Kotlin:
lateinit var navController: NavController
navController = Navigation.findNavController(view)
navController!!.navigate(R.id.action_confirmationFragment_to_secondActivity)
Related
I have a question regarding to NavController:
There is the scheme (sorry for my painting, hope it helps :D)
I have MainActivity and BottomNavigationView with 3 tabs: A, B, C.
When I tap to A it opens Fragment A1 and there is next button which opens Fragment A2.
In Fragment A2 there are buttons back, next, no problems with navigation here.
The problem is when I need to navigate from Fragment A2 to section B the same like a click on B in BottomNavigationView.
The problem is that it's different graph, how to switch them?
My ideas:
I found work-around: requireActivity().bottomBar.selectedItemId = R.id.graph_b but it's not good idea.
I would like to achieve it using navigation component. I was trying to do findNavController().navigate(R.id.graph_b), but it leads to crash:
java.lang.IllegalArgumentException: navigation destination
com.my.app.staging:id/graph_b is unknown to this NavController
How to make it using NavController ?
There is Google Example project with all architecture:
https://github.com/googlesamples/android-architecture-components/tree/master/NavigationAdvancedSample
And to simplify my question I've added a button in this project, where on click should opens different screen:
You graphs are defined in your Parent Activity and thats where you will be able to control them.
Your first way is actually the solution. Your graphs are defined within your activity. When you are inside any destinations inside your graph A (say A1, A2 etc.) they have no knowledge of the your other graphs B & C. The only way to get to the graph is through parent activity, and hence
requireActivity().bottomBar.selectedItemId = R.id.graph_b
The second way that you have tried will definitely not work because findNavController().navigate(R.id.graph_b) is used when you have nested navigation. In other words graph_b should be inside graph_a which is not your case.
That being said what you can do is just write
requireActivity().bottomBar.selectedItemId = R.id.graph_b
fancier. Instead of running inside your fragments, its better to run inside your activity.
// In your fragment
requireActivity?.moveToGraphB()
// and in your activity
fun moveToGraphB() {
bottomBar.selectedItemId = R.id.graph_b
}
Or more more fancier would be using SharedViewModel which I don't think is necessary.
In graph B you can add the line
where nav_graphA is the name of the graph you want to navigate to when the button is clicked. Then you can add
` <fragment
android:id="#+id/aboutFragment"
android:name="com.mycompany.aboutFragment"
tools:layout="#layout/fragment_about"
android:label="About" >
<action
android:id="#+id/action_aboutFragment_to_nav_graphA"
app:destination="#id/nav_graphA" />
</fragment>
`
to create the action to navigate when the button is clicked.
I launch a BottomSheetDialogFragment from the main fragment with the show function (navigation component can't start DialogFragments).
Now in the BottomSheetDialogFragment I have a button to move to a detail activity.
I have the BottomSheetDialogFragment defined in the graph (isolated) and it points to the detail activity.
But when I try to navigate, it can't find the navController. Is it possible to pass the navController to this isolated fragment?
MainFragment to Detail is working
DialogFragment to Detail is not working.
I tried:
- findNavController: navigation is not set
- activity.findNavController(...)
But when I try to navigate, it can't find the navController. That's how navigation-component work.
Is it possible to pass the navController to this isolated fragment? I hope you will not going with that, replacing navController is not the perfect solution for your case.
What to do?
You can have new nav.xml with new parent activity and the (isolated) fragment as child, and navigate from the BottomSheetDialogFragment to (isolated) fragment-activity.
Otherwise, I don't see any problems that prevent you from adding related fragments in one nav.xml.
Also you may need to obey for navigation-component contract, don't use show() function while using navigation-component, you may miss some advantages here!
You really don't need show function see:-
Android Activity as a dialog
Explanation:-
You can have a parent activity and set it's theme as Dialog, so all fragments (inside the nav.xml) will be dialogs..
I used this trick in one of my apps before.
I am attaching an OnDestinationChangedListener to my NavController in my Activity.
It works fine for all fragment destinations. However when I navigate to an activity destination my listener is not notified of the change.
override fun onStart() {
super.onStart()
navFragment.findNavController().addOnDestinationChangedListener { controller, destination, arguments ->
Log.e("Navigation", "id: ${destination.id} label: ${destination.label}")
}
}
How can I listen destination changes for activity destinations as well?
Documentation says, that:
The Navigation component is designed for apps that have one main
activity with multiple fragment destinations. The main activity is
associated with a navigation graph and contains a NavHostFragment that
is responsible for swapping destinations as needed. In an app with
multiple activity destinations, each activity has its own navigation
graph.
So for now I think that you have to keep track on your activities by yourself. :-( Navigation graphs seem to be "encapsulated" inside exactly one activity.
I am trying to use the navigation controller right now. I want to move from LoginFragment to HomeFragment. In LoginFragment I use this code below to move to HomeFragment.
Navigation.findNavController(view).navigate(homeDestination)
However, when I tap the back button in the HomeFragment, it will go back to LoginFragment, I expect that when I tap the button it will close the app.
In old way, if I use activity instead of using Fragment, I usually do something like this to get that expected behaviour:
val intent = Intent(this,HomeActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK.or(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
By using those flags, I use to get the expected behavior. But I don't how to implement the same behavior using the navigation controller.
Navigation offers a popUpTo and popUpToInclusive attributes for removing fragments from the back stack as part of a navigate() operation.
This can be set either in XML:
<!-- Add to your Navigation XML, then use navigate(R.id.go_home) -->
<action
android:id="#+id/go_home"
app:destination="#+id/home_fragment"
app:popUpTo="#+id/destination_to_pop"
app:popUpToInclusive="true"/>
Or set it programmatically:
NavOptions navOptions = new NavOptions.Builder()
.setPopUpTo(R.id.destination_to_pop, true)
.build();
Navigation.findNavController(view).navigate(homeDestination, navOptions)
You can also use the id of a <navigation> element as well.
I was following the answer of Ian but I had been having bad luck since I didn't know what the popUpTo would be.
So we have to use the id Of nav Graph there.
app:popUpTo="#+id/idOfNavGraph". //id of nav graph
<action
android:id="#+id/go_home"
app:destination="#+id/home_fragment"
app:popUpTo="#+id/idOfNavGraph". //id of nav graph
app:popUpToInclusive="true"/>
I think this should do the trick.
NavController controller = Navigation.findNavController(view);
controller.popBackStack(R.id.fragmentLogin, true);
controller.navigate(homeDestination)
Try this
val c = view.findNavController()
c.popBackStack() // current fragment will be pop up from the stack
c.navigate(DestinationFragmentID)
Obviously I'm late. Nonetheless I'd like to do my bit and expand Ian's answer. In app:popUpTo="#+id/destination_to_pop" the destination_to_pop really means the destination that you do not want to keep in the back stack. In your case, loging_fragment.
This way, when you navigate back from your HomeFragment the app will close since LoginFragment is not in the back stack anymore. You can find the related official documentation here.
scenario :
MainActivity.kt : activity_main.xml contains my_nav_host_fragment
in mobile_navigation.xml first_frag_dest is the startDestination and there is second_frag_dest also, with respective classes FirstFragment.kt and SecondFragment.kt.
I have setup bottom navigation for switch between two fragments using the Android Jetpack Navigation components.
When the Second Fragment is opened for the first time if FirbaseUser is null, A Welcome Activity is launched which is configured for GoogleSignInusing Firebase.
In WelcomeActivity.java I have a function updateUI(user: FirebaseUser) to update the UI.
Now, what code should I write in updateUI function such that I am redirected back to the Second Fragment.
Please tell how to do it using JetPack Navigation components.
Note: Principles of navigation say that only one activity must exist but here I am compelled to use an Activity for GoogleSignIn due to Firebase restrictions.
If you want to close your WelcomeActivity and return to the activity below it, have your updateUI method call finish():
Call this when your activity is done and should be closed