NullPointerException when trying to get NavController - android

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

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.

FragmentArgs class in not generated while passing value of one fragment to another by using Navigation components

This is my FragmentTwo fragment class code.
class FragmentTwo : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
// Inflate the layout for this fragment
val binding : FragmentTwoBinding = DataBindingUtil.inflate(inflater,R.layout.fragment_two, container, false)
var args = FragmentTwoArgs.fromBundle(arguments)
setHasOptionsMenu(true)
return binding.root
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater?.inflate(R.menu.overflow_menu,menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return NavigationUI.onNavDestinationSelected(item!!,findNavController())
|| super.onOptionsItemSelected(item)
}
}
This is my FragmentOne fragment class code:
class FragmentOne : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
// return inflater.inflate(R.layout.fragment_one, container, false)
val binding: FragmentOneBinding =
DataBindingUtil.inflate(inflater, R.layout.fragment_one, container, false)
binding.clickable = this
return binding.root
}
fun onClicking() {
//Toast.makeText(activity, "You clicked me.", Toast.LENGTH_SHORT).show()
//findNavController().navigate(R.id.action_fragmentOne_to_fragmentTwo)
findNavController().navigate(FragmentOneDirections.actionFragmentOneToFragmentTwo())
}
}
And this is my Navigation xml code.
<?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/navigation"
app:startDestination="#id/fragmentOne">
<fragment
android:id="#+id/fragmentOne"
android:name="com.example.fragmentpractise1.FragmentOne"
android:label="fragment_one"
tools:layout="#layout/fragment_one" >
<action
android:id="#+id/action_fragmentOne_to_fragmentTwo"
app:destination="#id/fragmentTwo" />
<argument
android:name="numViews"
app:argType="integer"
android:defaultValue="18" />
</fragment>
<fragment
android:id="#+id/fragmentTwo"
android:name="com.example.fragmentpractise1.FragmentTwo"
android:label="fragment_two"
tools:layout="#layout/fragment_two" />
<fragment
android:id="#+id/aboutFragment"
android:name="com.example.fragmentpractise1.AboutFragment"
android:label="fragment_about"
tools:layout="#layout/fragment_about" />
</navigation>
Now I am getting an error in FragmentTwo class code as FragmentTwoArgs class is not generated while assigning it to args variable. I am using Nav safe args and defined argument value through Nav graph in FragmentOne.
Any help would be appreciated.
You're using wrong Fragment to declare the arguments. If you want FragmentTwo to have arguments you should use the fragment in the navigation xml:
<fragment
android:id="#+id/fragmentTwo"
android:name="com.example.fragmentpractise1.FragmentTwo"
android:label="fragment_two"
tools:layout="#layout/fragment_two">
<argument
android:name="numViews"
app:argType="integer"
android:defaultValue="18" />
</fragment>
You might also want to use the lazy delegate navArgs():
private val args: FragmentTwoArgs by navArgs()

Unable to pass value from FragmentOne to FragmentTwo even after setting argument value for destination fragment in navigation

FragmentTwo fragment class code:
class FragmentTwo : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
// Inflate the layout for this fragment
val binding : FragmentTwoBinding = DataBindingUtil.inflate(inflater,R.layout.fragment_two, container, false)
var args = FragmentTwoArgs.fromBundle(arguments)
setHasOptionsMenu(true)
return binding.root
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater?.inflate(R.menu.overflow_menu,menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return NavigationUI.onNavDestinationSelected(item!!,findNavController())
|| super.onOptionsItemSelected(item)
}
}
FragmentOne fragment class code:
class FragmentOne : Fragment() {
var nameValue = "Abhas"
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
// return inflater.inflate(R.layout.fragment_one, container, false)
val binding: FragmentOneBinding =
DataBindingUtil.inflate(inflater, R.layout.fragment_one, container, false)
binding.clickable = this
binding.button.setOnClickListener {
findNavController().navigate(FragmentOneDirections.actionFragmentOneToFragmentTwo())
}
return binding.root
}
}
navigation xml code :
<?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/navigation"
app:startDestination="#id/fragmentOne">
<fragment
android:id="#+id/fragmentOne"
android:name="com.example.fragmentpractise1.FragmentOne"
android:label="fragment_one"
tools:layout="#layout/fragment_one" >
<action
android:id="#+id/action_fragmentOne_to_fragmentTwo"
app:destination="#id/fragmentTwo" />
</fragment>
<fragment
android:id="#+id/fragmentTwo"
android:name="com.example.fragmentpractise1.FragmentTwo"
android:label="fragment_two"
tools:layout="#layout/fragment_two" >
<argument
android:name="nameValue"
app:argType="string" />
</fragment>
<fragment
android:id="#+id/aboutFragment"
android:name="com.example.fragmentpractise1.AboutFragment"
android:label="fragment_about"
tools:layout="#layout/fragment_about" />
</navigation>
Now when I am setting args variable in FragmentTwo class, arguments showing error in fromBundle(arguments). I have tried giving argument in setOnclicklistener in FragmentOne while navigating but it's not asking for any kind of value in constructor. I am unable to understand why
arguments in fromBundle(arguments) of FragmentTwo class showing error.
Looks like you forget to declare argument inside the action(fragment one).
And you don't send anything from fragment_one.
You should add argument to the action inside the fragment_one in navigation xml :
<action
android:id="#+id/action_fragmentOne_to_fragmentTwo"
app:destination="#id/fragmentTwo">
<argument
android:name="nameValue"
app:argType="string"
android:defaultValue="default" />
</action>
then re-build the app - another navigation action method will be generated with a string argument.
FragmentOneDirections.actionFragmentOneToFragmentTwo(nameValue : String)
So you should put the value to this method in fragment one.
You can find detailed documentation by the link.

Saving fragment's view state

I use navigation component and I faced a pretty interesting problem with fragments: whenever I open fragment B from fragment A and then go back to fragment A, fragment A's view state is lost. I mean the view is being created again. The same thing happens if I use FragmentTransaction instead of navigation component. When I detach the fragment and then attach again, the view is being created again. Is there any possible way I can prevent the view from being destroyed or is there any way I can save the state of the view?
UPDATE
I wrote a simple code to demonstrate this behavior:
activity_main.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/navHostView"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.navHostView) as NavHostFragment?
val navController = navHostFragment!!.navController
navController.setGraph(R.navigation.navigation_main)
}
}
The navigation itself:
<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/navigation_main"
app:startDestination="#id/fragmentA">
<fragment
android:id="#+id/fragmentA"
android:name="com.sever.fragmentviewtest.FragmentA"
android:label="FragmentA"
tools:layout="#layout/fragment_a">
<action
android:id="#+id/action_fragmentA_to_fragmentB"
app:destination="#id/fragmentB" />
</fragment>
<fragment
android:id="#+id/fragmentB"
android:name="com.sever.fragmentviewtest.FragmentB"
android:label="FragmentB"
tools:layout="#layout/fragment_b" />
</navigation>
Fragment A
class FragmentA : Fragment() {
private var shouldSetText = true
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
retainInstance = true
val view = inflater.inflate(R.layout.fragment_a, container, false)
if (shouldSetText) { // Will be called only once
view.findViewById<TextView>(R.id.tvTest).text = "Some text"
shouldSetText = false
}
view.rootView.setOnClickListener {
findNavController().navigate(R.id.action_fragmentA_to_fragmentB)
}
return view
}
}
Fragment B
class FragmentB : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_b, container, false)
view.rootView.setOnClickListener {
findNavController().navigateUp()
}
return view
}
}
Important
The FragmentA's onCreateView is being called whenever I navigate back from FragmentB. And if I made some changes to FragmentA's views (like set text to a TextView) - they are lost after popping back stack from FragmentB. I tried to use navigateUp - the behavior is the same. And also retainInstance have not helped me to solve the issue.

Handle Graphs nesting from Parent Fragment into Tablayout Fragments

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" />

Categories

Resources