Masquerade
Masquerade

Reputation: 3890

IllegalStateException: Link does not have a NavController set

I'm using Android Navigation Component for Navigation. I have a LoginFragment which has a button to transition to SignUpFragment. On clicking the button I'm getting this error.

java.lang.IllegalStateException: View android.support.v7.widget.AppCompatButton{49d9bd1 VFED..C.. ...P.... 201,917-782,1061 #7f090172 app:id/signUpLink} does not have a NavController set

Here is my nav_graph.xml

<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        app:startDestination="@id/loginFragment">
        <fragment
            android:id="@+id/loginFragment"
            android:name="org.test.oe.app.core.auth.login.LoginFragment"
            android:label="login_fragment"
            tools:layout="@layout/login_fragment">
            <action
                android:id="@+id/action_loginFragment_to_signUpFragment"
                app:destination="@id/signUpFragment" />
          
        </fragment>
    </navigation>

Here is the code in LoginFragment for Navigation -

binding.signUpLink.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_loginFragment_to_signUpFragment, null));

Here is extract from activity layout file for NavHostFragment -

<FrameLayout
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    android:name="android.navigation.fragment.NavHostFragment"
    app:navGraph="@navigation/main_navigation"
    app:defaultNavHost="true"/>

Upvotes: 185

Views: 161236

Answers (22)

Dkathayat1
Dkathayat1

Reputation: 173

Make sure your not using

<androidx.fragment.app.FragmentContainerView
    android:id="@+id/frgContainerHome"
    android:name="androidx.navigation.fragment.NavHostFragment"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    app:defaultNavHost="true"/>

Use :

<fragment
        android:id="@+id/frgContainerHome"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:defaultNavHost="true"/

Upvotes: 2

Masquerade
Masquerade

Reputation: 3890

UPDATED SOLUTION

Actually, Navigation can't find NavController in FrameLayout. So replacing <FrameLayout> with <fragment> will make it work .

Add the following inside the <fragment> tag -

android:name="androidx.navigation.fragment.NavHostFragment"

After doing the changes, the code will look similar to this -

 <fragment
       android:id="@+id/fragment_container"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       app:layout_behavior="@string/appbar_scrolling_view_behavior"
       android:name="androidx.navigation.fragment.NavHostFragment"
       app:navGraph="@navigation/main_navigation"
       app:defaultNavHost="true"/>

Upvotes: 135

Dennis Gonzales
Dennis Gonzales

Reputation: 111

use onPostCreate() in MainActivity

Instantiate your lateinit var navController in onPostCreate() instead of onCreate() like so:

override fun onPostCreate(savedInstanceState: Bundle?) {
    super.onPostCreate(savedInstanceState)
    navController = findNavController(this, R.id.nav_fragment_container)
}

make sure your in your xml you are using: <androidx.fragment.app.FragmentContainerView />

Upvotes: 2

Sajawal Bashir
Sajawal Bashir

Reputation: 51

I have also faced this problem. My scenario was when i click on image in recycler view go to another fragment. below code was giving error enter image description here

When i change this code to below code it works well. NO ERROR enter image description here

Upvotes: 0

Hassan Badawi
Hassan Badawi

Reputation: 302

in my case, I forget to add the start destination

app:startDestination="@+id/navigation_home"

res ==> navigation ==> mobile_navigation.xml

<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/mobile_navigation.xml"
    app:startDestination="@+id/navigation_home">

Upvotes: 0

ritikmalhotra7
ritikmalhotra7

Reputation: 1

For this to work you have to override both onCreatedView and onViewCreated in all the fragments my code: `

     <FrameLayout
     android:id="@+id/fl"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
     <fragment
        android:id="@+id/fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:navGraph="@navigation/main_nav_graph"
        app:defaultNavHost="true"/>
        </FrameLayout>

        <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_navigation_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:menu="@menu/bottom_nav_menu" />

     

Upvotes: 0

Otziii
Otziii

Reputation: 2496

Updated answer for FragmentViewContainer

Make sure you have

android:name="androidx.navigation.fragment.NavHostFragment"

instead of the fragment you want as start fragment. (Start destination is defined in the Nav Graph).

Upvotes: 0

Muhammad Waqas
Muhammad Waqas

Reputation: 239

private lateinit var binding : ActivityNewsBinding

lateinit var viewModel: NewsViewModel

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    //setContentView(R.layout.activity_news)
    binding = ActivityNewsBinding.inflate(layoutInflater)
    setContentView(binding.root)

    val newsrepository = NewsRepository(ArticleDatabase(this))
    val viewModelProviderFactory = NewsViewModelProviderFactory(newsrepository)
    viewModel = ViewModelProvider(this,viewModelProviderFactory).get(NewsViewModel::class.java)

    
    val navHostFragment = supportFragmentManager.findFragmentById(R.id.newsNavHostFragment) as NavHostFragment
    val navController = navHostFragment.navController
    binding.bottomNavigationView.setupWithNavController(navController)


}

Upvotes: 0

Banana
Banana

Reputation: 2533

After doing some research I found this Issue also on Google's bugtracker. So here's the official solution in Java:

NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
                .findFragmentById(R.id.nav_host_fragment);
NavController navCo = navHostFragment.getNavController();

and here in Kotlin:

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

Upvotes: 74

Sana Ebadi
Sana Ebadi

Reputation: 7220

in my case (I was using BottomNavigation), in findNavController I was adding Framlayout Id! you should add the fragment Id like this:

in my xml:

<FrameLayout
            android:id="@+id/container_orders"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_above="@+id/bnv_main"
            android:visibility="gone">

            <fragment
                android:id="@+id/nav_orders"
                android:name="androidx.navigation.fragment.NavHostFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:defaultNavHost="false" />

        </FrameLayout>

in Kotlin class:

private val navOrdersController by lazy {
        mainActivity.findNavController(R.id.nav_orders).apply {
            graph = navInflater.inflate(R.navigation.main_navigation_graph).apply {
                startDestination = startDestinations.getValue(R.id.action_history)
            }
        }
    }

Upvotes: 0

Ramhawkz47
Ramhawkz47

Reputation: 81

In my case it was done by android studio...

<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:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/mobile_navigation" />

The above code is the working code that was replaced as shown below by a warning!

<androidx.fragment.app.FragmentContainerView
        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:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/mobile_navigation" />

Upvotes: 8

Dave Rincon
Dave Rincon

Reputation: 368

In my case I change my code

val action =
        StartFragmentDirections.actionStartFragmentToLoginFragment()
    Navigation.findNavController(view).navigate(action);

In onViewCreated()

Upvotes: 0

Jason Braithwaite
Jason Braithwaite

Reputation: 119

I had the following error:

MainActivity@9ff856 does not have a NavController set on 2131230894

I was using Bottom Navigation View:

The following worked for me:

val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottomNavigationView)

val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragment)
val navController = navHostFragment?.findNavController()
if (navController != null) {
    bottomNavigationView.setupWithNavController(navController)
}

Upvotes: 5

Vishal Mishra
Vishal Mishra

Reputation: 1

this code should work properly for kotlin

val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_loginFlow_fragment) as NavHostFragment
val navController = navHostFragment.navController
navController.navigate(R.id.to_dictionaryDownload1)

Upvotes: 0

Shivam Goel
Shivam Goel

Reputation: 375

Just replace <FrameLayout> With <fragment> and replace android:name="org.fossasia.openevent.app.core.auth.login.LoginFragment" with android:name="androidx.navigation.fragment.NavHostFragment"

Upvotes: 7

Valeriy Katkov
Valeriy Katkov

Reputation: 40702

The reason is that the fragment view isn't available inside the Activity.onCreate() method if you're adding it using FragmentContainerView (or just a FrameLayout). The proper way to get the NavController in this case is to find the NavHostFragment and get the controller from it. See the issue and the explanation.

override fun onCreate(savedInstanceState: Bundle?) {
   ...

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

Don't use <fragment> instead of <androidx.fragment.app.FragmentContainerView> as some other answers suggest. See the comment from the Google team in the issue I mentioned above.

You should always use FragmentContainerView. There are absolutely other fixes around window insets and layout issues that occur when a fragment's root layout is directly within other layouts such as ConstraintLayout, besides the underlying issues with the tag where fragments added via that tag go through lifecycle states entirely differently from the other Fragments added to the FragmentManager. The Lint check is there exactly because you absolutely should switch over to FragmentContainerView in all cases.

There's also an announcement from AndroidDevSummit 2019 which explains why FragmentContainerView was introduced, and another thread on SO about the difference: <androidx.fragment.app.FragmentContainerView> vs as a view for a NavHost

Upvotes: 49

Rawa
Rawa

Reputation: 13936

Officially recommended solution

Currently using the FragmentContainerView is not very friendly, you have to access it from the supportFragmentManager:

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

In my xml my FragmentContainerView looks like this:

<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:defaultNavHost="true"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toBottomOf="parent"
    app:navGraph="@navigation/nav_graph"
    />

This has been tested with androidx navigation version 2.3.0

I've removed the old answer because it has been clarified by Google devs that it is not the recommended solution anymore. Thanks @Justlearnedit, for posting a comment allowing me to update this issue.

Upvotes: 355

AndresElCaraDeMemela
AndresElCaraDeMemela

Reputation: 61

In Java try this below line:

Navigation.findNavController(findViewById(R.id.nav_host_fragment)).navigate(R.id.first_fragment);

Upvotes: 6

Amr263
Amr263

Reputation: 151

i faced this issue just now, but i was sure about my code and then realized that i have changed the location of the fragment from under the main package to another folder

so i solved the issue with Build-> clean then Build ->make project to let the IDE to change its Directions class

Upvotes: 0

butchy butchy
butchy butchy

Reputation: 61

I faced the same problem. So,instead of this,

binding.signUpLink.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_loginFragment_to_signUpFragment, null));

I used my NavHostFragment to find the NavHostFragment:

Button button = (Button)findViewById(R.id.button);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                Fragment navhost = getSupportFragmentManager().findFragmentById(R.id.fragment2);
                NavController c = NavHostFragment.findNavController(navhost);
                c.navigate(R.id.firstFragment);


            }
        });

fragment2 is navhostfragmentID.

Upvotes: 6

u_pendra
u_pendra

Reputation: 948

A weird thing happens to me, below code snippet was working on normal flow from Fragment1 to fragment2, but after coming to fragment1 and on again navigate fragment2, this was throwing the "Navigation controller not set for the view" error.

    binding.ivIcon.setOnClickListener(v -> {
            Openfragment2(v);});

private void Openfragment2(View view) {
    Navigation.findNavController(binding.ivIcon).navigate(R.id.fragment2);

}

Here problem was in view, in findNavController need to pass the onclicked view.

private void Openfragment2(View view) {
    Navigation.findNavController(view).navigate(R.id.fragment2);

}

Upvotes: 0

Ali Hasan
Ali Hasan

Reputation: 673

Use the view of fragment such as onViewCreated

 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    val navController = Navigation.findNavController(view)

    binding.signUpLink.setOnClickListener {
            navController.navigate(R.id.action_loginFragment_to_signUpFragment)
    }

Upvotes: 5

Related Questions