111

View Binding got released as part of Android Jetpack

Docs: https://developer.android.com/topic/libraries/view-binding

My question is, how to use view binding with custom views. Google documentation has only show-cased Activity and fragment.

I tried this, but nothing was shown.

LayoutInflater inflater = LayoutInflater.from(getContext());

And then, I used this one, but again, no luck.

LayoutInflater inflater = (LayoutInflater)
            getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

I guess maybe I don't target the correct layout inflater for my view but not sure.

Emad Razavi
  • 1,903
  • 2
  • 17
  • 24

6 Answers6

189

Just inform the root, and whether you want to attach to it

init { // inflate binding and add as view
    binding = ResultProfileBinding.inflate(LayoutInflater.from(context), this)
}

or

init { // inflate binding and add as view
    binding = ResultProfileBinding.inflate(LayoutInflater.from(context), this, true)
}

which inflate method to use will depend on the root layout type in xml.

Adam Johns
  • 35,397
  • 25
  • 123
  • 176
Androiderson
  • 16,865
  • 6
  • 62
  • 72
33

To use the view binding, you need to use the generated binding class not the LayoutInflater, for example, if the layout name is result_profile.xml then you need to use ResultProfileBinding as:

class CustomView @kotlin.jvm.JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
    
    private var binding: ResultProfileBinding
    
    init { // inflate binding and add as view
        binding = ResultProfileBinding.inflate(LayoutInflater.from(context))
        addView(binding.root)
    }
    
}
  1. Auto generated class : result_profile.xml -> ResultProfileBinding(name of layout, appended with Binding )

  2. Inflate the binding

    ResultProfileBinding.inflate(LayoutInflater.from(context))
    
  3. Use addView to add the view in the hierarchy as:

    addView(binding.root)
    

Note: If you are extending from ConstraintLayout(is the parent class) then use constraint set

JerabekJakub
  • 5,268
  • 4
  • 26
  • 33
Pavneet_Singh
  • 36,884
  • 5
  • 53
  • 68
  • 1
    Thanks. Your answer can be helpful in some parts, but I need a way to use view binding to inflate with root view not adding the view to the root view. – Emad Razavi Feb 27 '20 at 13:08
  • @EmiRaz if your custom view is created(java/kotlin) then add you custom view in the XML layout and then can directly access your custom view from the `binding` object as `binding.customview` or if you want to use view binding as it sounds (`how to use view binding with custom views`) then follow the answer. **FYI**: `inflater` is used to inflate new views using the xml layouts – Pavneet_Singh Feb 27 '20 at 13:17
  • 1
    If you inflate the view itself in the init block (as @EmiRaz is asking) you just end up in an infinite loop where the inflation calls the init block again. He is asking how to use `CustomViewBinding` inside of `CustomView` – Jason Toms Feb 27 '20 at 13:22
  • @Downvoter: may I know the reason of downvote? as I have clearly added the details about how to use custom view with view binding and added the missing details in the OP in comments – Pavneet_Singh Feb 27 '20 at 13:22
  • @JasonToms why would it create a loop, have you tested it? as we will be using the generated binding class of the layout in our custom view – Pavneet_Singh Feb 27 '20 at 13:26
  • @Pavneet_Singh As I commented earlier, your answer is true in some cases, but imagine that the custom class is not a ViewGroup, for instance, ImageView. so how is your answer applicable to that situation. to be clear, we don't have addView method there. – Emad Razavi Feb 27 '20 at 13:31
  • @EmiRaz The custom views with layouts are view group not View, View uses `onDraw` to draw on the canvas, I hope this clears the confusions form all the comments :) Read [more in the docs](https://developer.android.com/guide/topics/ui/custom-components) – Pavneet_Singh Feb 27 '20 at 13:36
  • @Pavneet_Singh Thanks, I get what you mean, but assume a condition where there is a CustomView that extends ImageView and you want to Extend that CustomView but it does not have methods you need (Programmatically). you can only set attributes to get what you need from that CustomView. so you have to create a layout and put the CustomView as is it's root view. and you need to inflate that particular XML layout file. is there an approach for that? – Emad Razavi Feb 27 '20 at 13:50
  • Your parent view is FrameLayout, so simply inflate into `this` as root: `CustomViewBinding.inflate(LayoutInflater.from(context), this)`. Your layout have to be `merge`. – Miroslav Michalec Feb 27 '20 at 14:09
  • @EmiRaz I am not sure what's your requirement in the recent comment but I also don't see any issue, `CustomVIew : Imageview` then `SomeVIew: CustomVIew` then you can put the xml tag of `SomeVIew` in layout and use it with the binding, to inflate, just use the binding class though just make sure to not create any cycle if there's any issue then please feel free to ask another question with problematic code :) – Pavneet_Singh Feb 27 '20 at 14:37
  • @MiroslavMichalec but my first intention is to use the same flow as docs and In this example, not sure about using `merge` in this example though it can be appropriate in different scenarios. – Pavneet_Singh Feb 27 '20 at 14:42
  • `addView()` is not necessary if you pass in true for the last param. `ResultProfileBinding.inflate(LayoutInflater.from(context), this, true)` See `Androiderson`'s answer. – Markymark Apr 21 '21 at 21:38
  • in which method we need to set the binding to null? – Pavel Poley Jun 19 '21 at 16:09
  • @PavelPoley to avoid memory leaks and unexpected behaviour in fragments, do it inside `onDestroyView`, when the view of the fragment no longer exists but the fragment is still there, not required in custom views. – Pavneet_Singh Jul 13 '21 at 21:11
14

You can initialize the view binding property right away

private val binding = CustomViewBinding.inflate(LayoutInflater.from(context), this)
Levon Petrosyan
  • 8,815
  • 8
  • 54
  • 65
9

If you are trying to use View Binding with the root view, this is working for me:

class CustomView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {

    private lateinit var binding: CustomViewBinding

    override fun onFinishInflate() {
        super.onFinishInflate()
        binding = CustomViewBinding.bind(this)
    }
}
Jason Toms
  • 850
  • 2
  • 10
  • 23
  • I am afraid you have some other case, because it doesn't work the way you suggest and crashes with NPE, because nothing gets generated for ids inside this custom view layout. – Volodymyr Buberenko Mar 30 '20 at 19:13
  • 6
    Could this leak memory? In fragments it's important to de-reference the binding in `onDestory` – A1m Aug 04 '20 at 17:59
1

This is the simplest kotlin answer I can think of. It's a custom view that just wraps a single TextView and provides an update(s:String) function to update the text.

<!-- view_stub.xml -->
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <TextView android:id="@+id/myTextView" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" />
</layout>

// StubView.kt
class StubView @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0
) : FrameLayout(context,attrs,defStyleAttr) {

    val binding = ViewStubBinding.inflate(context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater)
            .also { addView(it.root) }

    fun update(updatedText: String) {
        binding.myTextView.text = updatedText
    }
}

The two things I like about this answer are:

  1. binding is a val instead of a var. I try to limit the number of vars as much as possible.
  2. The addView is closely associated with the val binding using the also {} scope function instead of an init {} clause, making the instantiation of the View feel much more declarative.

One could argue that the addView() is really a side effect and should be in the init {} section so that it is separate from the declaration of the binding val. I would argue the opposite -- declaring a val then feeding it to a section of code that needs it does not feel like a side effect to me.

JohnnyLambada
  • 12,700
  • 11
  • 57
  • 61
1

You can use DataBindingUtil

binding = DataBindingUtil.inflate(
        LayoutInflater.from(context),
        R.layout.your_layout_id,
        this,
        true
    )