231

While working with view binding, I came across a couple of undocumented cases.

First: How do I get binding for included view layout parts? The main binding only sees items defined in the main layout.

Second: How do I get binding for merged layout parts. Again, the main binding only sees items in the main layout?

Adil Hussain
  • 30,049
  • 21
  • 112
  • 147
Artur Kasprzak
  • 5,938
  • 3
  • 9
  • 13
  • 2
    Hi I have written a blog post completely explaining view binding and along with handling of included tag with merge and include layouts checkout [Androidbites|ViewBinding](https://chetangupta.net/viewbinding/) – Chetan Gupta Dec 06 '20 at 10:54
  • The other simple way would be using the data binding library. Then wrap your XML layout with tag so that if you are using the library automatically generates the classes required to bind the views in the layout with your data objects. Honestly, I think it's the way to go. Follow the guide [here](https://developer.android.com/topic/libraries/data-binding) – Arun Gurung Apr 27 '20 at 10:54
  • Im kinda stuck here:https://stackoverflow.com/questions/67808297/viewbinding-of-included-library-will-not-generated/67819728#67819728 – SharePeng Hu Jun 04 '21 at 09:09
  • Just by the way, for anyone wondering how to use view-binding to set up the __appbar logo__, just use the biding object as: __binding.toolbar.setLogo(R.drawable.bar_logo);__. 'binding' and 'bar_logo' changes as per your need. – Ajowi Aug 13 '21 at 05:51

9 Answers9

341

In case of:

  1. Include with generic layout (not merge node), we need to assign ID to included part, this way in binding we will have access to included sub part
<include
    android:id="@+id/your_id"
    layout="@layout/some_layout" />

This way in your activity code:

private lateinit var exampleBinding: ActivityExampleBinding  //activity_example.xml layout

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    exampleBinding = ActivityExampleBinding.inflate(layoutInflater)
    setContentView(exampleBinding.root)
    //we will be able to access included layouts view like this
    val includedView: View = exampleBinding.yourId.idOfIncludedView
//[...]
}
  1. Include with merge block in external layout. We can't add ID to it because merge block is not a view. Let's say we have such eternal merge layout (merge_layout.xm):
<?xml version="1.0" encoding="utf-8"?>
<merge 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"
    tools:showIn="@layout/activity_example">

    <TextView
        android:id="@+id/some_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World" />
</merge>

To properly bind such merge layout we need to:

In your activity code:

private lateinit var exampleBinding: ActivityExampleBinding  //activity_example.xml layout
private lateinit var mergeBinding: MergeLayoutBinding  //merge_layout.xml layout

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    exampleBinding = ActivityExampleBinding.inflate(layoutInflater)
    //we need to bind the root layout with our binder for external layout
    mergeBinding = MergeLayoutBinding.bind(exampleBinding.root)
    setContentView(exampleBinding.root)
    //we will be able to access included in merge layout views like this
    val mergedView: View = mergeBinding.someView
//[...]
}
Albert Vila Calvo
  • 15,298
  • 6
  • 62
  • 73
Artur Kasprzak
  • 5,938
  • 3
  • 9
  • 13
  • 10
    had to add the `layout` tag inside the file that contains the merged views – João Carlos Jun 05 '20 at 13:49
  • 1
    I'm trying to do this in a fragment instead of an activity and it's not working. I have set global_header as id of my include item, and actually the autocompletion in android studio works perfectly well and autocompletes viewBindings.globalHeader, so it's generated for the binding, but then at runtime it crashes: " java.lang.NullPointerException: Missing required view with ID: com.daon.ps.daonsonboard:id/global_header" – Fran Marzoa Sep 28 '20 at 12:08
  • @FranMarzoa Are you sure you do all needed operations in Fragments onCreateView? like: `val binding = FrgamentSomethingSomething.inflate(layoutInflater)` and `return binding.root` – Artur Kasprzak Sep 29 '20 at 13:12
  • @FranMarzoa Also keep in mind that `binding.globalHeader` won't return view - it will be GlobalHeaderBinding (or whatever you called file with layout) and inside you will find all your included views. – Artur Kasprzak Sep 29 '20 at 13:21
  • It crashed while inflating the layout, I didn't even have a chance to access it. Anyway I found a solution and a good explanation of the issue here: https://stackoverflow.com/a/52343338/1160360 – Fran Marzoa Oct 01 '20 at 09:08
  • @ArturKasprzak: do i need to set `mergeBinding=null` when fragment/activity destroy with `onDestroy()`? – Trần Leo Sep 08 '21 at 14:20
  • @TrầnLeo I don't think it's needed. Of course keep in mind not to provide those bindings outside fragment/activity unless you want to have memory leak – Artur Kasprzak Sep 09 '21 at 12:46
  • not solve. any other way? – IntelliJ Amiya Oct 05 '21 at 08:56
  • You can use the "merge" binding method above with layouts that are `include`d as well. You need to `import` that binding explicitly. – CodeClown42 Dec 16 '22 at 15:51
  • java.lang.NullPointerException: Missing required view with ID: com.claimbuild:id/includeTopNavigation at Z0.b2.a(:86) at Z0.b2.d(:58) at Z0.b2.c(:48) at A1.d.g1(:53) – karan Jan 13 '23 at 12:07
  • im getting this error after using merge layout using view binding – karan Jan 13 '23 at 12:08
46

Regarding your first question, you can get a view binding for the included layout.

Here is a sample main_fragment.xml file:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view_main"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <include
        android:id="@+id/toolbar"
        layout="@layout/toolbar" />

</LinearLayout>

And MainFragment.java can be like this:

public class MainFragment extends Fragment {

    private MainFragmentBinding binding;
    private ToolbarBinding toolbarBinding;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {

        binding = MainFragmentBinding.inflate(inflater, container, false);
        toolbarBinding = binding.toolbar;

        return binding.getRoot();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        toolbarBinding = null;
        binding = null;
    }
}

Now you have two bindings: One corresponds to the main layout and the other corresponds to the included layout.

Adil Hussain
  • 30,049
  • 21
  • 112
  • 147
Emad Razavi
  • 1,903
  • 2
  • 17
  • 24
  • 2
    Very simple answer and uses the new syntax - all working for me in a non-Fragment Activity with similar syntax in `onCreate()`. Thanks. (Just having a bit of trouble using for a `DrawerLayout`) – Fat Monk Apr 16 '20 at 14:38
  • 1
    I'm getting a runtime NPE that says missing required view with ID blah. – Asim May 20 '20 at 08:41
  • 1
    @Asim Maybe if you change your included layout from merge to some viewGroup – Jesús Barrera Jun 28 '20 at 07:09
  • 1
    In Kotlin I have to cast the assignment (`toolbarBinding = binding.toolbar as ToolbarBinding`) or it is considered a `View` and I get an error. Oddly, instead using `binding.toolbar.something` will compile and run fine but appears as an error in AS. – CodeClown42 Feb 19 '23 at 17:50
  • 1
    ...Even better: Using the toolbar binding with the cast throws warning during build. "No cast needed" but in the editor the warning is "This cast cannot succeed" (and without it, the View typing error). – CodeClown42 Feb 19 '23 at 17:55
20

If you want to bind included layout then,

For Activity

YourMainLayoutBinding mainLayoutBinding = MainLayoutBinding.inflate(getLayoutInflater);

View view = mainLayoutBinding.getRoot();

YourIncludedLayoutBinding includedLayoutBinding = YourIncludedLayoutBinding.bind(View);

For Fragment

YourMainLayoutBinding mainLayoutBinding = MainLayoutBinding.inflate(inflater,container,false);

View view = mainLayoutBinding.getRoot();

YourIncludedLayoutBinding includedLayoutBinding = YourIncludedLayoutBinding.bind(View);

Make Sure if your main layout binding parent root is LinearLayout then, includedLayoutBinding parent layout also be a linear layout

Dr Mido
  • 2,414
  • 4
  • 32
  • 72
Manav Adhyaru
  • 211
  • 2
  • 3
  • 9
    No need to explicitly bind the included layout, it is done automatically if you specify an id for it: ``. After that the field `mainLayoutBinding.myToolbar` will contain a nested binding. – Dmitry K Jun 02 '21 at 14:07
  • 1
    @DmitryK actually it is needed, as otherwise Android Studio Arctic Fox gives errors `Cannot access class 'x'. Check your module classpath for missing or conflicting dependencies` – Michał Dobi Dobrzański Oct 27 '21 at 08:27
18

Suppose I included a layout in my activity_main.xml file as follows:

<include
    android:id="@+id/ll_layout1"
    layout="@layout/layout1"
    android:visibility="gone" />

And suppose I wanted to change its visibility. I can do so as follows:

activityMainBinding.llLayout1.root.visibility = View.VISIBLE
Adil Hussain
  • 30,049
  • 21
  • 112
  • 147
shivang
  • 266
  • 3
  • 7
  • root function not available, you can use `rootView` instead, I think it's the same – bobby I. Sep 08 '21 at 08:44
  • 1
    @bobbyI. It's Kotlin, so we are using property syntax, hence, just root. In java, you can use `getRoot()` instead to achieve the same. – shivang Sep 29 '21 at 11:18
3

In my case, I forgot to assign id to the include tag

Now, when you've assigned the id, you'll be able to get the binding object as,

YourMainLayoutBinding.YourIncludeTagIDLayoutBinding
Aziz
  • 1,976
  • 20
  • 23
1

Follow steps:-

  1. private val binding : FragmentBinding by viewBinding(FragmentBinding::bind)

  2. make sure to do the following in "onViewCreated(view: View, savedInstanceState: Bundle?)"

     val binding2 = binding.root.include_layout_id
    

e.g. val binding2 = binding.root.tool_bar_layout

Now access your include layout, views here. e.g.

binding2.textView.text = "your text"
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
user1035292
  • 1,378
  • 12
  • 14
1

Answering your first question in reference to a fragment, suppose you've included an "error_layout" in your MainFragment's xml file.

<include
    layout="@layout/error_layout"
    android:id="@+id/layout_error"
    android:visibility="gone"/>

Now in "error_layout" you have a button with the id : "btn_try_again". You want to set an on click listener to this button.

This is how you can get reference to "btn_try_again" using binding object for your fragment_main.xml file,

binding.layoutError.btnTryAgain
Abhishek Guru
  • 483
  • 4
  • 8
-1

Using the data binding library. Then wrap your XML layout with <layout> tag

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    ... 

    <include 
      android:id="@+id/toolbar"
      layout="@layout/toolbar" />
    
    ...

</LinearLayout>
</layout>

toolbar.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ImageView 
        android:id="@+id/ivImage"
        ... />

    <TextView 
        android:id="@+id/tvTitle"
        ... />


</LinearLayout>

MainActivity.kt

private lateinit var binding: ActivityMainBinding  

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

    // Access include layout views
    binding.toolbar.rootView.ivImage.setImageResource(R.drawable.ic_back_arrow)
    binding.toolbar.rootView.tvTitle.text = getString(R.string.home)
   
    ...
}
TrucLC
  • 17
  • 3
-2

In the include layout you must create a Container layout and put here the id.

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/example"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent">
    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
DavidUps
  • 366
  • 3
  • 9