Search code examples
androidkotlinmvvmdata-bindingviewmodel

The expression cannot be inverted, to be used in a two-way binding in EditText error


It is my first time using data binding, so I'm confused. Trying to implement two-way databinding in EditText with MVVM architecture and getting this error in my Build:

The expression 'viewmodelClientUrl.getValue()' cannot be inverted, so it cannot be used in a two-way binding

Details: There is no inverse for method getValue, you must add an @InverseMethod annotation to the method to indicate which method should be used when using it in two-way binding expressions

I can't understand what it means

There is my LoginViewModel:

class LoginViewModel(
private val repository: MainRepository): ViewModel() {

private var _clientUrl = MutableLiveData<String?>()
private var _username = MutableLiveData<String?>()
private var _password = MutableLiveData<String?>()
private val validationError = ValidationError()

val clientUrl: LiveData<String?>
    get() = _clientUrl
val username: LiveData<String?>
    get() = _username
val password: LiveData<String?>
    get() = _password

fun onClick(){
    val clientUrl = clientUrl.toString().trim()
    val username = username.toString().trim()
    val password = password.toString().trim()
    validateCredentials(clientUrl, username, password)
}

private fun validateCredentials(clientUrl: String, username: String, password: String): Boolean {

    if(!Patterns.WEB_URL.matcher(clientUrl).matches() || clientUrl.isEmpty()) {
        validationError.isUrlValid = false
        return false
    }
    if(username.isEmpty()) {
        validationError.isUsernameValid = false
        return false
    }
    if(password.isEmpty()) {
        validationError.isUsernameValid = false
        return false
    }
    return true
}

There is my layout:

<layout
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">

<data>
    <import type="android.view.View"/>
    <variable
        name="viewmodel"
        type="com.example.redmining.ui.login.LoginViewModel"/>
    <variable
        name="validationError"
        type="com.example.redmining.model.ValidationError"/>
</data>


<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    tools:context=".ui.login.LoginFragment"
    android:background="@color/white">

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/urlTextInputLayout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="24dp"
        android:layout_marginTop="170dp"
        android:layout_marginEnd="24dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/clientUrl"
            android:text="@={viewmodel.clientUrl}"
            android:fontFamily="@font/poppins_light"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/url_hint"
            android:inputType="textUri"
            android:selectAllOnFocus="true"
            />

    </com.google.android.material.textfield.TextInputLayout>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/urlTextInputLayout"
        app:layout_constraintStart_toStartOf="@id/urlTextInputLayout"
        android:text="Invalid URL"
        android:textColor="@android:color/holo_red_light"
        android:textSize="12sp"
        android:layout_marginTop="-8dp"
        android:layout_marginStart="5dp"
        android:visibility="@{validationError.urlValid ? View.GONE : View.VISIBLE}"/>


    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/usernameTextInputLayout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="24dp"
        android:layout_marginTop="4dp"
        android:layout_marginEnd="24dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/urlTextInputLayout">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/username"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:fontFamily="@font/poppins_light"
            android:hint="@string/login"
            android:inputType="textEmailAddress"
            android:selectAllOnFocus="true"
            android:text="@={viewmodel.username}" />

    </com.google.android.material.textfield.TextInputLayout>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/usernameTextInputLayout"
        app:layout_constraintStart_toStartOf="@id/usernameTextInputLayout"
        android:text="Enter your username"
        android:textColor="@android:color/holo_red_light"
        android:textSize="12sp"
        android:layout_marginTop="-8dp"
        android:layout_marginStart="5dp"
        android:visibility="@{validationError.usernameValid ? View.GONE : View.VISIBLE}"/>


    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/passwordTextInputLayout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="24dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="24dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/usernameTextInputLayout"
        app:passwordToggleEnabled="true">

        <com.google.android.material.textfield.TextInputEditText
            android:text="@={viewmodel.password}"
            android:id="@+id/password"
            android:fontFamily="@font/poppins_light"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/password"
            android:imeActionLabel="@string/log_in"
            android:imeOptions="actionDone"
            android:inputType="textPassword"
            android:selectAllOnFocus="true" />

    </com.google.android.material.textfield.TextInputLayout>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/passwordTextInputLayout"
        app:layout_constraintStart_toStartOf="@id/passwordTextInputLayout"
        android:text="Enter your password"
        android:textColor="@android:color/holo_red_light"
        android:textSize="12sp"
        android:layout_marginTop="-8dp"
        android:layout_marginStart="5dp"
        android:visibility="@{validationError.passwordValid ? View.GONE : View.VISIBLE}"/>


    <Button
        android:onClick="@{() -> viewmodel.onClick()}"
        android:id="@+id/login"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="48dp"
        android:layout_marginEnd="48dp"
        android:layout_marginTop="16dp"
        android:backgroundTint="@color/black"
        android:enabled="true"
        android:fontFamily="@font/poppins_light"
        android:paddingLeft="30dp"
        android:paddingTop="20dp"
        android:paddingRight="30dp"
        android:paddingBottom="20dp"
        android:text="@string/log_in"
        android:textAllCaps="true"
        android:textColor="@color/white"
        android:textSize="18sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/passwordTextInputLayout"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Solution

  • I think you don't need 2 way binding, because it will work fine without it.

        android:text="@{viewmodel.clientUrl}"
    

    Even if you need to use it then, there are 2 approaches :

    1st approach :

    Use MutableLiveData instead of LiveData, because LiveData doesn't provide setter. make _clientUrl non-private and :

            android:text="@{viewmodel._clientUrl}"
    

    2nd approach :

    If you want to use LiveData then use afterTextChanged to set the value :

    in ViewModel :

    fun updateClientUrl(s: Editable) {
        _clientUrl.value = s.toString();
    }
    

    In XML :

            android:text="@{viewmodel.clientUrl}"
            android:afterTextChanged="@{viewmodel.updateClientUrl}"
    

    All of the solutions will work :

    enter image description here