Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom View with Two-Way Databinding

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?

like image 430
sunilson Avatar asked Jul 16 '18 22:07

sunilson


People also ask

Can I use both DataBinding and ViewBinding?

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.

What is two-way DataBinding Android?

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.

What is two-way binding in MVVM?

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.

What is 2way binding?

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.


1 Answers

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) {
        ...
    }
like image 152
sunilson Avatar answered Sep 29 '22 11:09

sunilson