Handle Graphs nesting from Parent Fragment into Tablayout Fragments - android

When i navigate to the Tab fragment(having tablayout with viewpager)
I need to nest the tablayout fragments navigation graph with the main navigation graph.
Here is the code.
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"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/C_nav_graph"
app:startDestination="#id/C1_fragment">
<include app:graph="#navigation/tab_navigation" />
<fragment
android:id="#+id/C1_fragment"
android:name="com.example.nestednavigationgraphexample.Fragment_C1"
android:label="C1"
tools:layout="#layout/fragment_c1">
<action
android:id="#+id/C1_to_C2_fragment"
app:destination="#id/C2_fragment" />
</fragment>
<fragment
android:id="#+id/C2_fragment"
android:name="com.example.nestednavigationgraphexample.Fragment_C2"
android:label="C2"
tools:layout="#layout/fragment_c2" >
<action
android:id="#+id/action_C2_fragment_to_tabFragment"
app:destination="#id/tabFragment" />
</fragment>
<fragment
android:id="#+id/tabFragment"
android:name="com.example.nestednavigationgraphexample.Tab_Fragment"
android:label="tabFragment" />
</navigation>
Tab_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"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/tab_navigation"
app:startDestination="#id/cardFragment">
<fragment
android:id="#+id/cardFragment"
android:name="com.example.nestednavigationgraphexample.CardFragment"
android:label="CardFragment" >
<action
android:id="#+id/action_cardFragment_to_fragment_Tab_Sub"
app:destination="#id/fragment_Tab_Sub" />
</fragment>
<fragment
android:id="#+id/fragment_Tab_Sub"
android:name="com.example.nestednavigationgraphexample.Fragment_Tab_Sub"
android:label="fragment_fragment__tab__sub"
tools:layout="#layout/fragment_fragment__tab__sub" />
</navigation>
CardFragment
class CardFragment : Fragment(),View.OnClickListener {
private var counter: Int? = null
var navController: NavController?=null
private val COLOR_MAP = intArrayOf(
R.color.red_100, R.color.red_300, R.color.red_500, R.color.red_700, R.color.blue_100,
R.color.blue_300, R.color.blue_500, R.color.blue_700, R.color.green_100, R.color.green_300,
R.color.green_500, R.color.green_700
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (arguments != null) {
counter = arguments!!.getInt(ARG_COUNT)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? { // Inflate the layout for this fragment
return inflater.inflate(R.layout.card_fragment, container, false)
}
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
view.setBackgroundColor(ContextCompat.getColor(context!!, COLOR_MAP[counter!!]))
val textViewCounter = view.findViewById<TextView>(R.id.tv_counter)
textViewCounter.text = "Fragment No " + (counter!! + 1)
view.findViewById<Button>(R.id.button).setOnClickListener(this)
navController= Navigation.findNavController(activity!!,R.id.nav_host_fragment)
}
companion object {
private const val ARG_COUNT = "param1"
fun newInstance(counter: Int?): CardFragment {
val fragment = CardFragment()
val args = Bundle()
args.putInt(ARG_COUNT, counter!!)
fragment.arguments = args
return fragment
}
}
override fun onClick(v: View) {
when(v!!.id){
R.id.button->
navController!!.navigate(R.id.action_cardFragment_to_fragment_Tab_Sub)
}
}
}
I get the following crash when clicking on button in card Fragment
java.lang.IllegalArgumentException: navigation destination com.example.nestednavigationgraphexample:id/action_cardFragment_to_fragment_Tab_Sub is unknown to this NavController
My tab layout has 2 instances of CardFragment. I am missing on how to nest the two graphs. Please help me with the solution or if someone can refer a good working example, i would be grateful. Thanking in advance.

It would be nice to get the error logcat but I think you haven't provided the action from TabFragment. Here is the action required to include in your tabFragment like this code:
<fragment
android:id="#+id/tabFragment"
android:name="com.example.nestednavigationgraphexample.Tab_Fragment"
android:label="tabFragment">
<action
android:id="#+id/action_cardFragment_to_fragment_Tab_Sub"
app:destination="#id/tabSubFragment" />
</fragment>
<fragment
android:id="#+id/tabSubFragment"
android:name="com.example.nestednavigationgraphexample.Fragment_Tab_Sub"
tools:layout="#layout/fragment_fragment__tab__sub"
android:label="tabSubFragment" />

Related

How to set startDestination in Navigation Component based on the fragment/nav_graph I come from?

I'm using Navigation Component with Kotlin, and have three main tabs in nav_dashboard - Explore, My Groups and Profile. When I start application the startDestination is nav_explore, but then I want to go from there to different screens eg. CreateGroupFragment/nav_create_group and from there I have button that I want to redirect me to nav_dashboard but with My Groups selected - I guess that then the startDestination needs to be nav_profile. But how I can I implement that?
Dashboard Fragment (it contains Fragment Container View in XML):
internal class DashboardFragment : Fragment() {
lateinit var navigator: DashboardNavigator
private var _binding: FragmentDashboardBinding? = null
private val binding
get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentDashboardBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initBottomNavigation()
}
private fun initBottomNavigation() {
with(binding.bottomNavigationView) {
setupWithNavController(getDashboardNestedNavController())
}
}
private fun getDashboardNestedNavController(): NavController {
val navHostFragment = childFragmentManager.findFragmentById(R.id.nav_host_container) as NavHostFragment
val navGraph = navHostFragment.navController.navInflater.inflate(R.navigation.nav_dashboard_menu)
navHostFragment.navController.addOnDestinationChangedListener { _, destination, bundle ->
when (destination.id) {
R.id.exploreFragment, R.id.myGroupsFragment, R.id.profileFragment -> setBottomNavVisibility(View.VISIBLE)
else -> setBottomNavVisibility(View.GONE)
}
}
return navHostFragment.navController
}
private fun setBottomNavVisibility(visibility: Int) {
binding.bottomNavigationView.visibility = visibility
}
}
nav_dashboard:
<?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_dashboard_menu"
app:startDestination="#id/nav_explore">
<include app:graph="#navigation/nav_explore" />
<include app:graph="#navigation/nav_my_groups" />
<include app:graph="#navigation/nav_profile" />
</navigation>
nav_create_group:
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/nav_create_group"
app:startDestination="#id/createGroupFragment">
<include app:graph="#navigation/nav_create_group_map" />
<include app:graph="#navigation/nav_my_groups" />
<fragment
android:id="#+id/createGroupFragment"
android:name="com.wojciechkula.locals.presentation.creategroup.CreateGroupFragment"
android:label="#string/create_group_create_group"
tools:layout="#layout/fragment_create_group">
<action
android:id="#+id/openMap"
app:destination="#id/nav_create_group_map" />
<!-- Here I want another action to navigate to nav_dashboard with startDestination: nav_my_groups-->
</fragment>
</navigation>
First of all, you don't need to change the startDestination.
Secondly, why are you using multiple graphs? That usually makes things more complicated!
Anyway, you can just set the selected tab manually.
binding.bottomNavigationView.menu.findItem(R.id.tabId).isChecked = true
"tabId" is the id of the item in your bottom nav xml file.

Navigate to certain fragment from another dynamic module

Is there any way to navigate to a certain fragment from another dynamic module and not navigate to the start destination? If not, what are the alternatives?
<fragment
android:id="#+id/loginFragment"
android:name="com.example.feature.login.presentation.LoginFragment"
android:label="LoginFragment">
<action
android:id="#+id/actionLoginToHome"
app:destination="#id/featureHomeNavGraph" />
</fragment>
<include-dynamic
android:id="#+id/featureHomeNavGraph"
app:graphResName="feature_home_nav_graph"
app:moduleName="feature_home" />
While this code works fine, it navigates me to the start destination but I need to navigate to another one.
Solution 1: Navigate to specific fragment and do not use <include-dynamic>
<fragment
android:id="#+id/profileDetailsFragment"
android:name="com.example.feature.profiles.presentation.details.ProfileDetailsFragment"
android:label="Profile Details"
app:moduleName="feature_profiles" />
Solution 2: Create a new graph XML with different start destination.
A good way to do this for embedded nav graphs is to have a blank Navigation Fragment. You get your action to go to this fragment and then send it arguments on where to navigate next. Some sample code:
class NavigatorFragment : Fragment() {
companion object {
const val REQUEST_PAGE_NAME = "REQUEST_PAGE_NAME"
const val PAGE_TYPE_ACTIVATE_CARD = "PAGE_TYPE_ACTIVATE_CARD"
const val PAGE_TYPE_CREDIT_LIMIT = "PAGE_TYPE_CREDIT_LIMIT"
const val PAGE_TYPE_SCC_HUB = "PAGE_TYPE_SCC_HUB"
const val PAGE_TYPE_ABOUT_CARD = "about_card"
const val PAGE_TYPE_MAKE_PAYMENT = "PAGE_TYPE_MAKE_PAYMENT"
fun createBundle(pageName: String) =
Bundle().apply {
putString(REQUEST_PAGE_NAME, pageName)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
when (arguments?.getString(REQUEST_PAGE_NAME)) {
PAGE_TYPE_ACTIVATE_CARD ->
findNavController().navigate(R.id.to_activateSecuredCard)
PAGE_TYPE_CREDIT_LIMIT ->
findNavController().navigate(R.id.to_creditLimit)
PAGE_TYPE_ABOUT_CARD ->
findNavController().navigate(R.id.to_aboutSecuredCard, arguments)
PAGE_TYPE_SCC_HUB ->
findNavController().navigate(R.id.to_sccHub)
PAGE_TYPE_MAKE_PAYMENT ->
findNavController().navigate(R.id.to_sccMakePayment)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding: FragmentSecuredCardNavigatorBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_secured_card_navigator, container, false)
binding.lifecycleOwner = viewLifecycleOwner
if (findNavController()
.currentDestination.toString()
.contains(this.javaClass.name)
){
findNavController().popBackStack()
}
return binding.root
}
}
main navigation:
<?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"
xmlns:tools="http://schemas.android.com/tools"
app:startDestination="#+id/home_dashboard_fragment">
<include app:graph="#navigation/nav_graph_secured_card" />
<!-- rest of navigation graph-->
.....
</navigation>
secured card navigation:
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/nav_root_secured_card"
app:startDestination="#id/securedCardNavigator">
<fragment
android:id="#+id/securedCardNavigator"
android:name="com.greendotcorp.securedcard.fragment.NavigatorFragment">
<action
android:id="#+id/to_activateSecuredCard"
app:destination="#+id/activateSecuredCard"
app:popUpTo="#+id/securedCardNavigator"
app:popUpToInclusive="true" />
</fragment>
</navigation>
Navigating to secured card navigation:
findNavController().navigate(R.id.nav_root_secured_card, NavigatorFragment.createBundle(NavigatorFragment.PAGE_TYPE_SCC_HUB))

How to return back to fragment I was in right now in Navigation Component?

I have one Activity with four Fragments. Three of them are in DrawerLayout as Items. Around those three Fragments everything works fine. However, when I change to the last one, let's say TestFragment, that is not in DrawerLayout, by using navController.navigate(R.id.nav_test), and then come back to the HomeFragment by pressing on Back button, I can't enter in TestFragment any more.
Why do I can't reopen the left fragment again?
My codes:
MainActivity.kt:
class MainActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
appBarConfiguration = AppBarConfiguration(setOf(R.id.nav_home, R.id.nav_up, R.id.nav_scan, R.id.nav_tst), drawer_layout)
toolbar_layout.setupWithNavController(toolbar, navController, appBarConfiguration)
setupActionBarWithNavController(navController, appBarConfiguration)
nav_view.setupWithNavController(navController)
fab.setOnClickListener {v ->
navController.navigate(R.id.nav_tst)
}
override fun onSupportNavigateUp(): Boolean {
return findNavController(R.id.nav_host_fragment).navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
}
}
TstFragment:
class TstFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_tst, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
}
Menu of DrawerLayout:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
<group android:checkableBehavior="single">
<item
android:id="#+id/nav_home"
android:icon="#drawable/ic_home"
android:title="#string/home" />
<item
android:id="#+id/nav_up"
android:icon="#drawable/ic_up"
android:title="#string/up" />
<item
android:id="#+id/nav_scan"
android:icon="#drawable/ic_scan"
android:title="#string/scan" />
</group>
</menu>
Navigation:
<?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"
xmlns:tools="http://schemas.android.com/tools"
app:startDestination="#+id/nav_home">
<fragment
android:id="#+id/nav_home"
android:name="com.example.app.HomeFragment"
android:label="#string/home"
tools:layout="#layout/fragment_home" >
<action
android:id="#+id/action_nav_home_to_nav_tst"
app:destination="#id/nav_tst" />
<action
android:id="#+id/action_nav_home_to_nav_scan"
app:destination="#id/nav_scan" />
<action
android:id="#+id/action_nav_home_to_nav_up"
app:destination="#id/nav_up" />
</fragment>
<fragment
android:id="#+id/nav_up"
android:name="com.example.app.UpFragment"
android:label="#string/up"
tools:layout="#layout/fragment_up" >
<action
android:id="#+id/action_nav_up_to_nav_home"
app:destination="#id/nav_home" />
</fragment>
<fragment
android:id="#+id/nav_scan"
android:name="com.example.app.ScanFragment"
android:label="#string/scan"
tools:layout="#layout/fragment_scan" >
<action
android:id="#+id/action_nav_scan_to_nav_home"
app:destination="#id/nav_home" />
</fragment>
<fragment
android:id="#+id/nav_tst"
android:name="com.example.app.TstFragment"
android:label="#string/tst"
tools:layout="#layout/fragment_tst" >
<action
android:id="#+id/action_nav_tst_to_nav_home"
app:destination="#id/nav_home" />
</fragment>
</navigation>
I could resolve my problem. For those who look for a solution. I need to remove the fab from the MainActivity and call it in my HomeFragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
requireActivity().findViewById<FloatingActionButton>(R.id.fab).setOnClickListener {
Navigation.findNavController(requireActivity(), R.id.nav_host_fragment)
.navigate(R.id.nav_tst)
}
}
}
And also I had to add the following line in onViewCreated for leaving the TstFragment:
requireActivity().onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
Navigation.findNavController(view).popBackStack(R.id.nav_tst, true))
}
}
Exactly in that way I could solve it.
getSupportFragmentManager().beginTransaction().replace(R.id.frag_frame, fragment).addToBackStack("just add any text").commit();

NullPointerException when trying to get NavController

I cannot understand what is the reason for the error java.lang.NullPointerException: null cannot be cast to non-null type androidx.navigation.fragment.NavHostFragment
This is my host fragment:
class HostFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root = inflater.inflate(R.layout.fragment_host, container, false)
// error here
val navHostFragment = requireActivity().supportFragmentManager.findFragmentById(R.id.hostFragment) as NavHostFragment
//
val navController = navHostFragment.navController
root.host_text_view.setOnClickListener {
Log.d("Tag", "clicked")
navController.navigate(R.id.action_hostFragment_to_secondFragment)
}
return root
}
}
I want to navigate to SecondFragment by clicking on the textView. And back.
And nav graph .xml file:
<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_graph"
app:startDestination="#id/hostFragment">
<fragment
android:id="#+id/hostFragment"
android:name="com.example.test.HostFragment"
android:label="fragment_host"
tools:layout="#layout/fragment_host" >
<action
android:id="#+id/action_hostFragment_to_secondFragment"
app:destination="#id/secondFragment" />
</fragment>
<fragment
android:id="#+id/secondFragment"
android:name="com.example.test.SecondFragment"
android:label="fragment_second"
tools:layout="#layout/fragment_second" >
<action
android:id="#+id/action_secondFragment_to_hostFragment"
app:destination="#id/hostFragment" />
</fragment>
</navigation>
The Navigation graph has its final form like:
best way to use Navigation Graph is after the view is created, since some view takes time to create so u might get this exception as your view is still in processing or creating the view.
so to avoid this , u can use OnViewCreated() as
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
navController = Navigation.findNavController(view)
view.imgNumbrLookUp.setOnClickListener {
navController.navigate(R.id.action_homeFragment_to_webActivity)
}
}
You don't need to find your navHostFragment to navigate
val root = inflater.inflate(R.layout.fragment_host, container, false)
root.host_text_view.setOnClickListener {
Log.d("Tag", "clicked")
if (findNavController().currentDestination?.id == hostFragment)
findNavController().navigate(R.id.action_hostFragment_to_secondFragment)
}
You can check for Kotlin methods to navigate
https://developer.android.com/guide/navigation/navigation-navigate

Android Studio Navigation Library problem

I want to use Navigation in my app.
declared in main_activity
<?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:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!-- Main content -->
<fragment
android:id="#+id/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/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
then I want to go from Fragment1 to Fragment2 using action
UPDATE I am added more code
class LoginFragment2 : Fragment(){
private lateinit var viewModel : LoginViewModel2
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fr_login, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initViews()
}
private fun initViews() {
btn_registration.setOnClickListener {
// Navigation.findNavController(it).navigate(R.id.action_loginFragment2_to_newTypeAccountFragment)
// navController = Navigation.findNavController(view)
// findNavController(it).navigate(R.id.action_loginFragment2_to_newTypeAccountFragment)
this.findNavController().navigate(R.id.action_loginFragment2_to_newTypeAccountFragment)
}
}
I tried several options (but not one did not help me)
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.app.peshkariki, PID: 22658
java.lang.IllegalStateException: View androidx.constraintlayout.widget.ConstraintLayout{ca39b84
V.E...... ........ 0,0-720,1360} does not have a NavController set
how to work with it correctly?
Navigation graph (In fact, it is very large, but I will post a small part related to this Fragment)
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/nav_graph"
app:startDestination="#id/mainActivity">
<fragment
android:id="#+id/loginFragment2"
android:name="com.app.peshkariki.newPesh.ui.login.LoginFragment2"
android:label="LoginFragment2"
tools:layout="#layout/fr_login">
<action
android:id="#+id/action_loginFragment2_to_newTypeAccountFragment"
app:destination="#id/newTypeAccountFragment" />
</fragment>
</navigation>
You called NavHostFragment but you don't need to.
Change it to
btn_registration.setOnClickListener {
this.findNavController()
.navigate(R.id.action_loginFragment2_to_newTypeAccountFragment)
}
Put this in onViewCreated:
val navHostFragment = supportFragmentManager.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
Also you might wanna do this course

Categories

Resources