I have a custom view that extends LinearLayout and inflates a Layout which contains a couple of Views.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
xmlns:android="http://schemas.android.com/apk/res/android">
<EditText
android:id="@+id/voice_edittext"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:hint="Add answer here"
/>
<ImageButton
android:id="@+id/microphone_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_mic_black_24dp"
/>
<ImageButton
android:id="@+id/delete_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_cancel_black_24dp"
android:visibility="gone"
/>
</LinearLayout>
I now want to two-way bind the text value of the Edittext when I use this view, like this:
<com.sunilson.quizcreator.presentation.views.EditTextWithVoiceInput
android:id="@+id/form_question"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginTop="10dp"
app:editTextValue="@={viewModel.observableText}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
For this I created some Binding Adapters
@BindingAdapter("editTextValueAttrChanged")
fun setListener(editTextWithVoiceInput: EditTextWithVoiceInput, listener: InverseBindingListener) {
editTextWithVoiceInput.voice_edittext.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(p0: Editable?) {
listener.onChange()
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
})
}
@BindingAdapter("editTextValue")
fun setTextValue(editTextWithVoiceInput: EditTextWithVoiceInput, value: String?) {
if (value != editTextWithVoiceInput.voice_edittext.text.toString()) editTextWithVoiceInput.voice_edittext.setText(value)
}
@InverseBindingAdapter(attribute = "editTextValue")
fun getTextValue(editTextWithVoiceInput: EditTextWithVoiceInput): String? {
return editTextWithVoiceInput.voice_edittext.text.toString()
}
This is the code of the view:
class EditTextWithVoiceInput(context: Context, attributeSet: AttributeSet) : LinearLayout(context, attributeSet) {
init {
val inflater = context?.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val view = inflater.inflate(R.layout.voice_edittext, this, true)
view.microphone_button.setOnTouchListener { p0, p1 ->
...
}
}
}
The problem now is that when the Fragment containing the view is started, I get this error:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.sunilson.quizcreator, PID: 12627
java.lang.NullPointerException: Attempt to invoke virtual method 'void com.sunilson.quizcreator.presentation.views.EditTextWithVoiceInput.setTag(java.lang.Object)' on a null object reference
at com.sunilson.quizcreator.databinding.FragmentAddQuestionBinding.<init>(FragmentAddQuestionBinding.java:112)
at android.databinding.DataBinderMapperImpl.getDataBinder(DataBinderMapperImpl.java:15)
at android.databinding.DataBindingUtil.bind(DataBindingUtil.java:199)
at android.databinding.DataBindingUtil.inflate(DataBindingUtil.java:130)
at android.databinding.DataBindingUtil.inflate(DataBindingUtil.java:95)
at com.sunilson.quizcreator.presentation.SingleActivity.fragments.AddQuestionFragment.AddQuestionFragment.onCreateView(AddQuestionFragment.kt:34)
at android.support.v4.app.Fragment.performCreateView(Fragment.java:2425)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1460)
at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1784)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1852)
at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:802)
at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2623)
at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2410)
at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2365)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2272)
at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:733)
at android.os.Handler.handleCallback(Handler.java:789)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:180)
at android.app.ActivityThread.main(ActivityThread.java:6944)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:835)
What am I missing here?
There is nothing ViewBinding can do that DataBinding cannot but it costs longer build times. Remember you don't need to use both, if you are using DataBinding, there is no need adding ViewBinding.
Two-way Data Binding is a technique of binding your objects to your XML layouts so that the layout can send data to your binding object. This is compared to a “traditional” or “one-way” Data Binding setup, where data would only move from your binding object to the layout.
Two-way data binding is nothing but updating the data source if there are any changes in the layout and vice versa. Two-way data binding is not applicable for all the views in Android. For example, using two-way data binding in EditText makes sense because users can update the data in the view.
Two-way binding gives components in your application a way to share data. Use two-way binding to listen for events and update values simultaneously between parent and child components. See the live example / download example for a working example containing the code snippets in this guide.
Ok, so my problem was that my custom view had incorrectly implemented the constructor functions. As I inflate this view in XML, I needed the constructor where I get passed the Attributeset to pass that Attributeset to the super constructor, which I did not do. Without that, my view had no attributes and could not be found via it's ID etc.
Now I have two constructors, depending on if I inflate from XML or code:
constructor(context: Context, optional: Boolean) : super(context) {
...
}
constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With