23

I use Android Studio 3.6-RC1 and build tools version 3.6.0-rc01 and encountered an issue with ViewBinding feature:

I have activity_test.xml file with the following markup:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include
        android:id="@+id/view_merged"
        layout="@layout/merge_view" />
</LinearLayout>

And merge_view.xml with following markup:

<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Merge view" />
</merge>

Activity code looks like the following:

class TestActivity : AppCompatActivity() {

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

        binding.viewMerged.label.text = "New text"

    }
}

Problem is when I try to access TextView from merged layout, the app throws an exception with the message java.lang.NullPointerException: Missing required view with ID: viewMerged.

The generated binding class looks like the following:

public final class ActivityTestBinding implements ViewBinding {
  @NonNull
  private final LinearLayout rootView;

  @NonNull
  public final MergeViewBinding viewMerged;

  private ActivityTestBinding(@NonNull LinearLayout rootView,
      @NonNull MergeViewBinding viewMerged) {
    this.rootView = rootView;
    this.viewMerged = viewMerged;
  }

  @Override
  @NonNull
  public LinearLayout getRoot() {
    return rootView;
  }

  @NonNull
  public static ActivityTestBinding inflate(@NonNull LayoutInflater inflater) {
    return inflate(inflater, null, false);
  }

  @NonNull
  public static ActivityTestBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable ViewGroup parent, boolean attachToParent) {
    View root = inflater.inflate(R.layout.activity_test, parent, false);
    if (attachToParent) {
      parent.addView(root);
    }
    return bind(root);
  }

  @NonNull
  public static ActivityTestBinding bind(@NonNull View rootView) {
    // The body of this method is generated in a way you would not otherwise write.
    // This is done to optimize the compiled bytecode for size and performance.
    String missingId;
    missingId: {
      View viewMerged = rootView.findViewById(R.id.view_merged);
      if (viewMerged == null) {
        missingId = "viewMerged";
        break missingId;
      }
      MergeViewBinding viewMergedBinding = MergeViewBinding.bind(viewMerged);
      return new ActivityTestBinding((LinearLayout) rootView, viewMergedBinding);
    }
    throw new NullPointerException("Missing required view with ID: ".concat(missingId));
  }
}

Am I missing something or there is no way to access views from included layouts with tags or it is not yet shipped in Android Studio 3.6-RC1?

Somesh Kumar
  • 8,088
  • 4
  • 33
  • 49

2 Answers2

61

I wrote an article about using ViewBinding with <merge> tag which you can find here

Basically, what you need to do is

  • Don't give <include> tag any ID.
  • Call bind() method of generated merge layout binding, passing the root view of the layout you included your layout in.
  • Access your view from the object of merge binding

For example, you have merge_view.xml so you'll have MergeViewBinding class generated and this is how you will access the view from this layout.

class TestActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityTestBinding.inflate(layoutInflater)
        val mergeBinding = MergeViewBinding.bind(binding.root)
        setContentView(binding.root)
        mergeBinding.label.text = "New text"
    }
}
Somesh Kumar
  • 8,088
  • 4
  • 33
  • 49
  • 1
    Unfortunately, it is not my question, so can't accept it. However, I upvoted your answer and mentioned it in related issue: https://issuetracker.google.com/issues/147792710 – Volodymyr Buberenko Feb 19 '20 at 09:23
  • Not working for me when inflating a layout for a RecyclerView ViewHolder (haven't tried other situations). I get "Missing required view with ID: text1" which is an id in the merge layout. Main layout is a CardView with one child: the include for merge layout – Mark Feb 19 '20 at 10:20
  • I think the reason is I had multiple views with ID (@android:id/text1) in the hierarchy (not spotted because lots of nested includes) and this seems to confuse the Binding – Mark Feb 19 '20 at 10:49
  • @VolodymyrBuberenko Didn't see that. It seems that the bind() method is not there for or just a workaround. let's see what they comment. – Somesh Kumar Feb 19 '20 at 11:34
  • In my case I have a layout that includes another layout multiple times (note: there is no merge layout). The problem occurs when the included layout's root has an ID. So I just needed to remove that (refer to the ID of the include tag instead). – Mark Feb 19 '20 at 12:12
  • I think the "multiple times" thing is irrelevant. – Mark Feb 19 '20 at 12:17
  • This is a good approach. However the bind call will try to cast the root layout of the merged layout to the root layout of the activity layout. This is causing a ClassCastException. What worked for me ironically was val mergeViewBinding = MergeViewBinding.bind(binding.root.findViewById(R.id.root_id_in_mergelayout) – Lakshman Chilukuri Feb 29 '20 at 12:03
  • @LakshmanChilukuri i think this is the current workaround, but, view binding should prevent us using `findViewById`, and looks like google still working on the issue, and it is available only in Android Studio 4.0 & above (beta & canary) – mochadwi Mar 06 '20 at 04:21
  • I've tried this approach, unfortunately, it only works if we did the above approach, when using dynamic views inflation (in runtime), it doesn't works T_T @SomeshKumar – mochadwi Mar 08 '20 at 09:35
  • (AS 3.6.0) I've also found the error happens when using DataBinding in only some of the layouts involved (`` being the root of only some layouts). I know this question seems more about the lighter ViewBinding, but that was my issue, and that may also cause issues with @SomeshKumar answer ('missing tag' errors). Ultimately, because I was using DataBinding, setting an ID for my `` tags worked fine as long as all layouts involved had `` as the root element, and I didn't have to keep track of multiple Bindings as described by @SomeshKumar. – mkuech Mar 31 '20 at 22:55
  • for future readers: this also doesn't works for `` that has multiple child, it won't gives NPE for first child, but others will throws NPE – mochadwi Apr 25 '20 at 19:13
  • @mochadwi `` usually has multiple children... it works for me. You might be doing something wrong. – Somesh Kumar Apr 28 '20 at 05:00
  • that's weird, lemme try it again for other usecase @SomeshKumar – mochadwi May 02 '20 at 19:09
  • I keep getting "view must have a tag" – Paul Okeke Dec 12 '20 at 09:46
  • Have edited another method also , without using mergeBinding – Yahya M Jul 14 '22 at 09:47
1

Simply use the binding of the view/file. Here is an example:

import ch.zkb.mobile.bank.databinding.MyFragmentBinding
import ch.zkb.mobile.bank.databinding.MyMergeBinding

class MyFragment : (R.layout.my_fragment) {

    private val binding by viewBinding(MyFragmentBinding::bind)
    private val mergeBinding by viewBinding(MyMergeBinding::bind)

    ...
}

View example:

<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"
    android:orientation="vertical">

    <include layout="@layout/my_merge" />

</LinearLayout>

Make sure, you build it first, so the Binding is generated.

Javatar
  • 2,518
  • 1
  • 31
  • 43
  • 1
    From which lib is ` by viewBinding`? – NickUnuchek Jan 18 '22 at 11:14
  • 1
    Checkout https://medium.com/flobiz-blog/fragment-view-binding-initialisation-using-delegates-8cd50b41e1d2 for more information. There are also more other pages. Search for "FragmentViewBindingDelegate" - makes you life much simpler ;) – Javatar Jan 18 '22 at 16:27