Sebastien
Sebastien

Reputation: 4305

Missing required view with ID with view binding and navigation component

I am trying to migrate my project to view binding and I get an exception when I start my app.

My main activity contains a NavHostFragment like so:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
        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"
        android:orientation="vertical">

    ...

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

    ...

</LinearLayout>

And the first fragment loaded by default in the NavHostFragment is implemented like so:

class ToolListFragment : Fragment(R.layout.fragment_tool_list) {
    ...
    private var _binding: FragmentToolListBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentToolListBinding.inflate(layoutInflater, container, false)
        return binding.root
    }
    ...
}

And here is the relevant part of the fragment's layout:

<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"
        tools:context=".ToolListFragment"
        android:orientation="vertical"
        android:background="@android:color/white">

    <com.google.android.material.tabs.TabLayout
            android:id="@+id/tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:tabMode="fixed"
            app:tabGravity="fill">
        <com.google.android.material.tabs.TabItem
                android:id="@+id/tab_around_me"
                android:layout_height="wrap_content"
                android:layout_width="wrap_content"
                android:text="@string/tool_filter_around_me"/>
        <com.google.android.material.tabs.TabItem
                android:id="@+id/tab_assigned_to_me"
                android:layout_height="wrap_content"
                android:layout_width="wrap_content"
                android:text="@string/tool_filter_assigned_to_me"/>
        <com.google.android.material.tabs.TabItem
                android:id="@+id/tab_all_tools"
                android:layout_height="wrap_content"
                android:layout_width="wrap_content"
                android:text="All"/>
    </com.google.android.material.tabs.TabLayout>
    ...
</LinearLayout>

As you can see, there is a TabItem with id tab_all. And yet here is the exception that crashes my app:

E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.herontrack.dev, PID: 12477
java.lang.NullPointerException: Missing required view with ID: com.herontrack.dev:id/tab_all_tools
    at com.herontrack.databinding.FragmentToolListBinding.bind(FragmentToolListBinding.java:133)
    at com.herontrack.databinding.FragmentToolListBinding.inflate(FragmentToolListBinding.java:78)
    at com.herontrack.ToolListFragment.onCreateView(ToolListFragment.kt:107)
    at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2950)
    at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:515)
    at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:282)
    at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:112)
    at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1636)
    at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3112)
    at androidx.fragment.app.FragmentManager.dispatchViewCreated(FragmentManager.java:3049)
    at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:2975)
    at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:543)
    at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:282)
    at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:112)
    at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1636)
    at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3112)
    at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:3056)
    at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:251)
    at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:473)
    at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:210)
    at com.herontrack.MainActivity.onStart(MainActivity.kt:100)
    at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1435)
    at android.app.Activity.performStart(Activity.java:8024)
    at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3475)
    at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221)
    at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201)
    at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173)
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loop(Looper.java:223)
    at android.app.ActivityThread.main(ActivityThread.java:7656)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

So it crashes on this line in ToolListFragment.onCreateView()

_binding = FragmentToolListBinding.inflate(layoutInflater, container, false)

But I really don't understand why.

Upvotes: 5

Views: 11540

Answers (2)

NSV.
NSV.

Reputation: 61

I found out if your use in fragment:

getLayoutInflater().inflate(R.layout.example_view, 
viewgroup, false);

In side of the onCreateView(LayoutInflater, ViewGroup, Int) method, you can do this and then store var view on it: like:

View variable = getLayoutInflater().inflate(R.layout.example_view, 
    viewgroup, false);
TabItem item = variable.findViewById(R.id.tabLayoutItem);
/*
Other code
*/

It will work flawlessly. However I hugely agree with @Susan Thapa's answer and currently I am using his solution. Provided this answer just incase's somebody else can't follow that answer, because of xyz reason.

Upvotes: 0

Susan Thapa
Susan Thapa

Reputation: 581

The problem that you are facing is that TabItem is just a dummy view. Looking in the source code you can see that

/**
 * TabItem is a special 'view' which allows you to declare tab items for a {@link TabLayout} within
 * a layout. This view is not actually added to TabLayout, it is just a dummy which allows setting
 * of a tab items's text, icon and custom layout. See TabLayout for more information on how to use
 * it.
 *
 * @attr ref com.google.android.material.R.styleable#TabItem_android_icon
 * @attr ref com.google.android.material.R.styleable#TabItem_android_text
 * @attr ref com.google.android.material.R.styleable#TabItem_android_layout
 * @see TabLayout
 */
//TODO(b/76413401): make class final after the widget migration
public class TabItem extends View {

So the ViewBinding fails to find the Tab with the ID that you have specified as this is not added to the view hierarchy. To fix this you have to remove the id from the TabItem. If you need to access Tab you can use the following code.

// to access first tab
binding.tabs.getTabAt(0)

Upvotes: 29

Related Questions