表达式不能倒置,要用在EditText错误的双向绑定中

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

我是第一次使用数据绑定,所以我很困惑。尝试使用 MVVM 架构在 EditText 中实现双向数据绑定并在我的构建中出现此错误:

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

没看懂什么意思

有我的 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
}

这是我的布局:

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

我认为您不需要 2 路绑定,因为没有它也能正常工作。

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

即使你需要使用它,也有两种方法:

第一种方法:

使用 MutableLiveData 而不是 LiveData,因为 LiveData 不提供 setter。 使 _clientUrl 非私有并且 :

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

第二种方法:

如果你想使用 LiveData 然后使用 afterTextChanged 设置值:

在 ViewModel 中:

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

在XML中:

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

所有解决方案都有效: