5

Normally, I use val navController: NavController = findNavController(R.id.nav_host_fragment) in Code A to find NavController, it's based R.id.nav_host_fragment.

Now I use view binding in the app just like Code B, how can I NavController if I use view binding ?

BTW, in my mind R.id.nav_host_fragment will not be available in view binding , right?

Code A

class TasksActivity : AppCompatActivity() { 

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.tasks_act)

        val navController: NavController = findNavController(R.id.nav_host_fragment)
     }
 
}

Code B

class TasksActivity : AppCompatActivity() {
    
    private lateinit var binding: TasksActBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = TasksActBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)       
      
        //val navController: NavController = findNavController(R.id.nav_host_fragment)       
    }

}

tasks_act.xml

<androidx.drawerlayout.widget.DrawerLayout
    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/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".tasks.TasksActivity"
    tools:openDrawer="start">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <com.google.android.material.appbar.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:minHeight="?attr/actionBarSize"
                android:theme="@style/Toolbar"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
        </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/nav_graph" />

    </LinearLayout>

    ..

</androidx.drawerlayout.widget.DrawerLayout>
HelloCW
  • 843
  • 22
  • 125
  • 310

3 Answers3

4

The code B should still be working fine .

I made a look into findNavController() . This is an extension function for simpifying the code , the code for extension function is

fun Activity.findNavController(@IdRes viewId: Int): NavController =
    Navigation.findNavController(this, viewId)

Now looking in to the code of findNavController() inside Navigation we see below

 @NonNull
    public static NavController findNavController(@NonNull Activity activity, @IdRes int viewId) {
        View view = ActivityCompat.requireViewById(activity, viewId);
        NavController navController = findViewNavController(view);
        if (navController == null) {
            throw new IllegalStateException("Activity " + activity
                    + " does not have a NavController set on " + viewId);
        }
        return navController;
    }

The viewId we are passing in argument is used in the first line

View view = ActivityCompat.requireViewById(activity, viewId);

now looking into requireViewById() inside ActivityCompat we see

  @NonNull
    public static <T extends View> T requireViewById(@NonNull Activity activity, @IdRes int id) {
        if (Build.VERSION.SDK_INT >= 28) {
            return activity.requireViewById(id);
        }

        T view = activity.findViewById(id);
        if (view == null) {
            throw new IllegalArgumentException("ID does not reference a View inside this Activity");
        }
        return view;
    }

for api 28 and plus the method which gets called is

@NonNull
    public final <T extends View> T requireViewById(@IdRes int id) {
        T view = findViewById(id);
        if (view == null) {
            throw new IllegalArgumentException("ID does not reference a View inside this Activity");
        }
        return view;
    }

So as long as the view (inside which the nav_host_fragment is present) is attached to activity the code which you wrote for finding the nav controller should work completely fine .

class TasksActivity : AppCompatActivity() {
    
    private lateinit var binding: TasksActBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = TasksActBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)       //you are attaching view to activity here , make sure you always call this before the next line , else you will get IllegalStateException 
      
        val navController: NavController = findNavController(R.id.nav_host_fragment)       
    }

}

I have not tested the code personally but from what I see it should work perfectly fine.

Manohar
  • 22,116
  • 9
  • 108
  • 144
2

We are using ViewBinding to get a reference of view itself. If you are using viewbinding in TextView, you will get a reference of the View.

In the case of NavCotroller, If you use Viewbinding, you will get a reference to a fragment, while findNavController expect ViewId of type integer.

findNavController(@IdRes viewId: int)
MustafaKhaled
  • 1,343
  • 9
  • 25
  • Thanks! but `findNavController(@IdRes viewId: int)` require the paramter `viewId`, is it available in view binding? – HelloCW Jul 12 '20 at 23:45
  • I'm not sure, but you can check this answer: https://stackoverflow.com/questions/60733289/navigation-with-view-binding – MustafaKhaled Jul 14 '20 at 11:36
  • `viewId` will be available, binding is mostly syntax sugar, so that don't need to use findViewById for every single view, e.g. you can use `binding.toolbar` rather than `val toolbar= findViewById(R.id.toolbar)`. You still can, but why would you, if you have binding? – Stachu Jul 24 '20 at 11:11
2

The correct working way is as follows:

enter image description here

FragmentContainerView has to be accessed from the supportFragmentManager:

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

val navController = navHostFragment.navController
Sweta Jain
  • 3,248
  • 6
  • 30
  • 50