I have a problem in my application when using nested graph in my nave graph xml file
It's my nav_graph file
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/nav_graph"
app:startDestination="#navigation/nav_entries">
<include app:graph="#navigation/nav_entries"/>
<include app:graph="#navigation/nav_states"/>
<include app:graph="#navigation/nav_calendar"/>
<include app:graph="#navigation/nav_more"/>
</navigation>
and this is fragment container view in activity_main
<androidx.fragment.app.FragmentContainerView
android:id="#+id/fragmentContainerView"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph" />
And this is code from main activity to setup bottom navigation view with nave controller
private fun setupUi() {
val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
navController = navHostFragment.navController()
binding.bottomNavigationView.setupWithNavController(navController)
}
But when I run the app, It will crash and this is error :
navigation destination com.iranmobiledev.moodino:navigation/nav_entries is not a direct child of this NavGraph
Help me to fix.
It's incorrect.
In navigation, not all nested navigation graphs should be defined with include, and a node(fragment) should be defined as the start destination. You can define other fragments in the nested navigation graph and use it with the include tag.
Use this code:
app:startDestination="#id/first_fragment_id">
Instead of:
app:startDestination="#navigation/nav_entries">
I faced the same issue.
instead of adding the graph in xml, inflate it programmatically.
the issue will be resolved.
Related
I'm currently working on a multi-module application which is structured as below and I can't get the back navigation to function correctly when navigating between feature modules. Either nothing happens or the app closes instead of just going back to the previous destination.
- app
- feature-explore
- feature-search
- feature-detail
Each of the feature modules has it's own navigation graph which is brought together in the main navigation graph located in the app module. The app module contains a single MainActivity with a FragmentContainerView and BottomNavigationView in its layout...
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/main_bottomnav"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="#menu/menu_bottomnav" />
<androidx.fragment.app.FragmentContainerView
android:id="#+id/main_navhost"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/nav_main" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
The BottomNavigationView is linked to a menu to hook up feature-explore and feature-search modules as main destinations with their own navigation graphs...
class MainActivity : AppCompatActivity() {
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(LayoutInflater.from(this))
setContentView(binding.root)
navController = (supportFragmentManager.findFragmentById(R.id.main_navhost) as NavHostFragment).navController
binding.mainBottomnav.setupWithNavController(
navController
)
}
}
I've then set up a global action in the main navigation graph to allow each of the feature-explore and feature-search modules to navigate to the feature-detail module (e.g. clicking an item in a list opens up the detail of that item via a separate feature module).
This is the main navigation graph...
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/nav_main"
app:startDestination="#id/nav_explore">
<include app:graph="#navigation/nav_detail" />
<include app:graph="#navigation/nav_explore" />
<include app:graph="#navigation/nav_search" />
<action
android:id="#+id/action_global_nav_detail"
app:destination="#id/nav_detail">
<argument
android:name="id"
app:argType="long" />
</action>
</navigation>
Switching between menu items works fine as does navigating using the global action above to move to the nav_detail graph.
From the fragment in that nav_detail graph though I would expect the device back button to take the user back to the previous view in the back stack. Instead nothing happens and the user stays on the detail fragment (the start destination of nav_detail).
If I update the global action to specify a popUpTo value then MainActivity is closed altogether...
<action
android:id="#+id/action_global_nav_detail"
app:destination="#id/nav_detail"
app:popUpTo="#id/nav_explore"
app:popUpToInclusive="false">
<argument
android:name="id"
app:argType="long" />
</action>
Anyone have any suggestions on how the get the back navigation from nav_detail to behave as expected?
Note - I'm using Navigation Components v2.5.3.
After further investigation I found that the issue wasn't actually to do with the navigation itself. The back press was actually going back to the previous fragment.
I was however using LiveData to hold an ID of the selected item and, as the view model and the LiveData value was retained, it was observed again immediately after navigating back and therefore navigated to the detail fragment again.
See similar issue here with a link to options for changing approach with the LiveData to avoid this problem.
I have a fragment container, which contains multiple other fragments, but when I am trying to replace one of the fragments inside the fragment container(to reload that fragment), the fragment does get replaced but instead of only that particular fragment getting replaced the whole container is getting replaced by that fragment and even when navigating to other fragments only that fragment is showing up.
I only want to replace one particular fragment present in the container not the whole container.
The fragment I want to replace is TroubleshootFragment. The code I am using to replace the fragment is :
val newfrag = TroubleshootFragment.newInstance()
val ftr = (this.activity)?.supportFragmentManager?.beginTransaction()
?.replace(R.id.container, newfrag)?.commit()
The xml file for container is :
<RelativeLayout 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/rootView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/background_image"
tools:context=".view.HomeActivity">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#id/bottom_navigation"
android:layout_below="#id/logoImageView"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_main" />
The xml file for the fragment is :
<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_main.xml"
app:startDestination="#id/aboutFragment">
<fragment
android:id="#+id/troubleshootFragment"
android:name="com.p.b.view.fragment.TroubleshootFragment"
android:label="Troubleshoot"
tools:layout="#layout/troubleshoot_layout">
<action
android:id="#+id/action_troubleshootSuccessFragment"
app:destination="#id/troubleshootSuccessFragment" />
</fragment>
I have tried the following code as well :
val ftr = (this.activity)?.supportFragmentManager?.beginTransaction()
?.replace(R.id.troubleshootFragment, newfrag)?.commit()
but this causes the app to crash, then how should I replace the fragment so that only Troubleshoot Fragment gets replaced and not the whole container containing other multiple fragments?
Edit : The log shows this :
I/FragmentNavigator: Ignoring navigate() call: FragmentManager has already saved its state when trying to navigate to other fragments.
I have a button in fragment A and when I click on fragment A, I want it to be redirected to fragment B. How to achieve this in kotlin? Right now I am using this code but app crash.
btntest.setOnClickListener {
var intent = Intent(view.context, FragmentB::class.java)
startActivity(intent)
}
I'd suggest you take a look on the navigation component. First, you create a Navigation resource file (usually referred to as Nav Graph), where you add the fragments you wish to connect.
Make sure to give an ID to each fragment (that's what you'll use to navigate between them later). Additionally, you can use app:startDestination in order to set the first fragment to be shown.
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
app:startDestination="#id/blankFragment">
<fragment
android:id="#+id/blankFragment"
android:name="com.example.cashdog.cashdog.BlankFragment"
android:label="#string/label_blank"
tools:layout="#layout/fragment_blank" />
</navigation>
Then, in the activity/fragment that will be hosting the fragments, add a FragmentContainerView and set the graph to the one you created.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
Finally, in order to navigate between fragments, use this in the Activity that is hosting the navigation (use getActivity() if a Fragment is hosting the navigation).
// as per defined in your FragmentContainerView
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
// Navigate using the IDs you defined in your Nav Graph
navController.navigate(R.id.blankFragment)
Source: https://developer.android.com/guide/navigation/navigation-getting-started
Edit: if you want to access the controller within your Fragment, use activity?.supportFragmentManager to get the fragment manager.
Try this in From Fragment, after you have set action in nav graph xml.
findNavController().navigate(R.id.action_from_fragmentA_to_fragmentB)
set in adapter click listner
loadFragment(SettingFragment())
private fun loadFragment(homeFragment: SettingFragment) {
val transaction = (context as AppCompatActivity).supportFragmentManager.beginTransaction()
transaction.replace(R.id.container,homeFragment)
transaction.addToBackStack(null)
transaction.commit()
}
Currently I have the following scenario: a user must sign in to use the app. This means I've used 2 nav_graphs, a main graph for everything and then a nested home graph for the views after you have signed in.
After signing in, a bottom navigation bar should appear to change tabs in the home graph.
I have the following home_fragment.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.home.HomeFragment">
<fragment
android:id="#+id/home_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="#navigation/home_graph"
app:defaultNavHost="true"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_navigation"
app:menu="#menu/navigation_items"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
And I want to be able to change tabs in the bottom navigation view, so I setup this logic in HomeFragement.kt using bottomNavigationView.setOnNavigationItemSelectedListener.
Unfortunetly, when I try to fetch the home_nav_host_fragment in home_fragment.xml I cannot because homeNavController = findNavController() in the fragment can only find the main_nav_host_fragment that's in the main activity.
I want findNavController() to return the home_nav_host_fragment instead but because this method only searches for parent NavControllers and not ones on the same level it cannot find the one I want.
Is there a better structure that will provide a solution to this issue? Thanks
This isn't the correct approach. Instead, you should listen for navigation events and hide the BottomNavigationView when you're on the login graph:
navController.addOnDestinationChangedListener { _, destination, _ ->
if(destination.parent!!.id == R.id.login_graph) {
toolbar.visibility = View.GONE
bottomNavigationView.visibility = View.GONE
} else {
toolbar.visibility = View.VISIBLE
bottomNavigationView.visibility = View.VISIBLE
}
}
Then, you can use just a single graph, following the best practices for user login.
Better use popUpTo combined with popUpToInclusive to create a one-way navigation action - in order to use a single one graph, instead of two disconnected graphs. This only should be applied to the first 1-2 fragments in the graph, so that eg. the back button cannot navigate back to the splash-screen or to the login-screen, because these actions do not count towards "the expected behavior".
<fragment
android:id="#+id/splashFragment"
android:name="com.acme.fragment.SplashFragment"
tools:layout="#layout/fragment_splash"
android:label="fragment_splash">
<action
android:id="#+id/action_splashFragment_to_loginFragment"
app:destination="#id/loginFragment"
app:popUpTo="#id/splashFragment"
app:popUpToInclusive="true"/>
</fragment>
I have a project with Fragments and Activities. I want to try Navigation element, but I can't add Fragments on my graph - in the destination list, I have only Activities. Why does it happen?
Androidx, Java.
UPD: I can't find the correct tag for this navigation element, so only the Android tag present.
Gragle:
implementation 'android.arch.navigation:navigation-fragment:1.0.0'
implementation "android.arch.navigation:navigation-ui:1.0.0"
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity">
<fragment
android:id="#+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/main_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity:
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
The screen where I have no Fragments on the destination list:
And, I think it can be important, my preview of MainActivity:
I don't know why, but it gray.
UPD1: red mark on last screen is:
A <fragment> tag allows a layout file to dynamically include different layouts at runtime. At layout editing time the specific layout to be used is not known. You can choose which layout you would like previewed while editing the layout.
I think it's explain why my fragment grey on preview - layout is unknown on runtime. So, my problem is not connecting with it.
It's resolve itselfs when i create one fragment by hands on my navigation graph, and do "Invalidate/restart".
To resolve this issue you have to add tools:layout line to your fragment tag: tools:layout="#layout/fragment_sample_name"
It should looks like this in navigation graph code view
<fragment
android:id="#+id/sampleFragment"
android:name="com.example.sampleFragment"
android:label="#string/sample_name"
tools:layout="#layout/fragment_sample">
</fragment>
Be sure navigation tag have xmlns:tools="http://schemas.android.com/tools" added
<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_sample_stackoverflow"
app:startDestination="#id/sampleFragment">