Jeremi
Jeremi

Reputation: 1474

Navigation Component: Cannot find NavController

I am using Navigation Component for navigating in my app. It works fine inside fragments but it fails to find the nav host in the activity that holds the actual navigation host.

I am trying to open a new fragment when the user clicks on FAB, which I included in Main activity's XML. When I call findNavController() it fails to find the controller. The nav host controller is in the XML layout. I can't understand why it fails to find it.

MainActivity

class MainActivity : AppCompatActivity(), OnActivityComponentRequest {
    override fun getTabLayout(): TabLayout {
        return this.tabLayout
    }

    override fun getFap(): FloatingActionButton {
        return this.floatingActionButton
    }

    private lateinit var tabLayout: TabLayout
    private lateinit var floatingActionButton: FloatingActionButton

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)
        this.tabLayout = tabs
        this.floatingActionButton = fab

        fab.setOnClickListener {

         it.findNavController().navigate(R.id.addNewWorkoutFragment)

        }
    }
}

Activity main XML

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
        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=".domain.MainActivity"
        android:animateLayoutChanges="true">

    <com.google.android.material.appbar.AppBarLayout
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:theme="@style/AppTheme.AppBarOverlay">

        <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                app:layout_scrollFlags="scroll|enterAlways"
                app:popupTheme="@style/AppTheme.PopupOverlay"/>

        <com.google.android.material.tabs.TabLayout
                android:id="@+id/tabs"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

            <com.google.android.material.tabs.TabItem
                    android:text="Test 1"
                    android:layout_height="match_parent"
                    android:layout_width="match_parent"/>

            <com.google.android.material.tabs.TabItem
                    android:text="Test 2"
                    android:layout_height="match_parent"
                    android:layout_width="match_parent"/>
        </com.google.android.material.tabs.TabLayout>

    </com.google.android.material.appbar.AppBarLayout>

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


    <com.google.android.material.bottomappbar.BottomAppBar
            android:id="@+id/bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"/>


    <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_anchorGravity="right|top"
            app:layout_anchor="@+id/bar"
            android:src="@drawable/ic_add_black_24dp"/>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

Upvotes: 34

Views: 80164

Answers (12)

Mohamed Mohamed Taha
Mohamed Mohamed Taha

Reputation: 162

You can use binding.root.findNavController().navigate(id)

Upvotes: 1

BCS Software
BCS Software

Reputation: 1905

Retrieve it later, in onPostCreate. like this:

@Override
public void onPostCreate(Bundle savedInstanceState ) {
    super.onPostCreate(savedInstanceState);
    
    navController = Navigation.findNavController(this, R.id.fragment_container_view);        
}

Upvotes: 4

hba
hba

Reputation: 7780

Kotlin Only

Android Navigation KTX provides an extension function for Activity

All I had to do was

findNavController(R.id.myNavHostFragment).navigate(R.id.to_someFragment)

I do have the following dependencies in my app/gradle.build.kts file (

implementation ("androidx.navigation:navigation-fragment-ktx:2.3.5")
implementation ("androidx.navigation:navigation-runtime-ktx:2.3.5")
implementation ("androidx.navigation:navigation-ui-ktx:2.3.5")

Using Directions

Alternatively you could also use generated Directions class. Since this is a global action the Directions class is found in the NavigationDirections.to_someFragment()

//very useful if you're passing args then you could do something like
//NavigationDirections.to_someFragment(myArg)    
NavigationDirections.to_someFragment()findNavController(R.id.myNavHostFragment).navigate(NavigationDirections.to_someFragment())

Side-note the specific extension function is being contributed from the runtime-ktx aar

Upvotes: 3

Hussien Fahmy
Hussien Fahmy

Reputation: 1285

The best way to access navController in OnCreate of Activity is to access it after inflating the layout that can achieved by kotlin extention function doOnPreDraw()

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    private val navController: NavController
        get() = Navigation.findNavController(this, R.id.nav_host_fragment)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.root.doOnPreDraw {
            // here you can safely use navController
            binding.toolbar.setupWithNavController(navController)
        }
    }
}

Upvotes: 0

Timur
Timur

Reputation: 760

This is Extension version for Kotlin:

 fun AppCompatActivity.findNavController(
    @IdRes navHostViewId: Int
): NavController {
    val navHostFragment =
        supportFragmentManager.findFragmentById(navHostViewId) as NavHostFragment

    return navHostFragment.navController
}

The navHostViewId is id of your androidx.fragment.app.FragmentContainerView.

Upvotes: 1

lvl3ha
lvl3ha

Reputation: 19

I found this answer helpful. In your activity paste this code

Navigation.findNavController(this, R.id.nav_host_fragment).navigate(R.id.yourDestination)

Upvotes: 1

LightMan
LightMan

Reputation: 3575

I was getting the same exeception until I managed to get the navController this way:

val navHostFragment = supportFragmentManager.findFragmentById(R.id.my_fragment_container_view_id) as NavHostFragment
val navController = navHostFragment.navController

Obtained from this answer:

Before finding the nav controller, you need to set the view, if you are using binding:

binding = BaseLayoutBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)

If not:

setContentView(R.layout.activity_main)

Use the corresponding binding for fragments.

Upvotes: 29

Slava Senchenko
Slava Senchenko

Reputation: 51

The problem might be that FAB was added to the activity or a fragment different from the one used by NavHost fragment. In this case, when you call it.findNavController() it cannot find the navigation controller.

You can either check that your FAB belongs to the fragment pulled by NavHost or call an Activity's findNavController(<id>) and pass it id of the fragment you're looking up

override fun onCreate(savedInstanceState: Bundle?) {
        ...
        fab.setOnClickListener {
            findNavController(R.id.nav_host_fragment)
                .navigate(R.id.addNewWorkoutFragment)

        }
    }

Upvotes: 3

Peterstev Uremgba
Peterstev Uremgba

Reputation: 729

you can still get access in your oncreate by doing

    //the id would be the id of the Fragment Element In Your Activity XML File
    val navGraph = Navigation.findNavController(this, R.id.activity_main)
    binding.mainFab.setOnClickListener {
        navGraph.navigate(R.id.destinationFragment)
    }

Upvotes: 1

JunaidKhan
JunaidKhan

Reputation: 734

In Java you can find NavController inside activity this way:

Navigation.findNavController(this,R.id.nav_host).navigate(R.id.YourFragment);

Upvotes: 18

Anmol
Anmol

Reputation: 8670

Try setting up onClickListener of Fab button in onStart of the Activity as in onCreate Activity is just inflating the View and haven't set the NavHostController. So if you setup onClickListener in onStart of activity is will work as expected.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    setSupportActionBar(toolbar)
    this.tabLayout = tabs
    this.floatingActionButton = fab
  }

override fun onStart() {
    super.onStart()
    floatingActionButton.setOnClickListener {
      it.findNavController().navigate(R.id.addNewWorkoutFragment)
    }
  }

Upvotes: 17

Jeremi
Jeremi

Reputation: 1474

Turns out that activity that holds navigation controller... doesn't have navigation component.

The solution is to manually set the NavController to each view contained in the activity.

val navController = findNavController(R.id.nav_host_fragment)
Navigation.setViewNavController(fab, navController)

Now this would work:

fab.setOnClickListener {

    it.findNavController().navigate(R.id.addNewWorkoutFragment)

}

I still don't understand why this works the way it works so any explanation would be more than welcome :)

As of now, Android API simply doesn't make much sense.

Source: Navigate to fragment on FAB click (Navigation Architecture Components)

Upvotes: 2

Related Questions