android - 如何在 MVVM Android 中使用数据绑定(bind)处理 ViewModel 中的 onClick 或 onTouch 类事件

标签 android mvvm android-databinding android-viewmodel android-mvvm

我浏览了许多与 相关的博客。带有数据绑定(bind)的 MVVM 模型 .
由于与 ViewModel 的数据绑定(bind)使得编写 junit 测试用例变得容易。

我想知道,如何实现像 OnTouchListener 这样的监听器事件, OnClickListener , OnFocusChangeListener在 ViewModel 中进行数据绑定(bind),这将使编写单元测试用例变得容易。

我已经使用黄油刀库进行绑定(bind),并通过它执行 OnTouch事件,我的问题是,在 Activity 中实现监听器而不是在 ViewModel 中直接实现它是一种正确的方法吗?
请引用下面的带有 MVVM 结构的 LoginScreen 代码:

LoginActivityNew.java

public class LoginActivityNew extends AppCompatActivity {

@BindView(R.id.et_password)
AppCompatEditText etPassword;

private LoginViewModel loginViewModel;

ActivityLoginBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

        binding = DataBindingUtil.setContentView(this, R.layout.activity_login);
        loginViewModel = ViewModelProviders.of(this).get(LoginViewModel.class);
        binding.setViewModel(loginViewModel);
        binding.setLifecycleOwner(this);

        ButterKnife.bind(this);

        binding.buttonLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Common common = new Common(getApplicationContext());
                common.isInternetAvailable(LoginActivityNew.this, new Common.InternetStateListener() {
                    @Override
                    public void onNetworkStateObtain(boolean isAvailable) {
                        loginViewModel.getAuthenticateTokenData().observe(LoginActivityNew.this, new Observer<TokenResponse>() {
                            @Override
                            public void onChanged(@Nullable TokenResponse tokenResponse) {
                                if (tokenResponse != null) {
                                    loginResponseHandler(tokenResponse, tokenResponse.getUserName(), tokenResponse.getPassword());
                                } else {
                                    Log.d("jdhadd","TokenResponse == null");
                                }
                            }
                        });
                    }
                });
            }
        });

}


private void loginResponseHandler(final TokenResponse tokenResponse, final String username, final String password) {
    switch (tokenResponse.getState()) {
        case ApiState.LOADING:
            Log.d("testData","Loading");
            break;
        case ApiState.COMPLETED:

            Log.d("testData","COMPLETED");
            break;
        case ApiState.FAILURE:
            Log.d("testData","FAILURE");

            break;
        default:
    }
}

@OnClick(R.id.et_user_name)
void onTouchUserName() {
    loginViewModel.resetEditTextField("username");
}

@OnClick(R.id.et_password)
void onTouchPassword() {
    loginViewModel.resetEditTextField("password");
}
}

LoginViewModel.java
public class LoginViewModel extends AndroidViewModel {


public final MutableLiveData<String> userName = new MutableLiveData<>();
public final MutableLiveData<String> password = new MutableLiveData<>();
public final MutableLiveData<String> userNameError = new MutableLiveData<>();
public final MutableLiveData<String> passwordError = new MutableLiveData<>();
public final MutableLiveData<Boolean> userNameErrorVisibility = new MutableLiveData<>();
public final MutableLiveData<Boolean> passwordErrorVisibility = new MutableLiveData<>();
public final MutableLiveData<Boolean> isViewPasswordIconVisible = new MutableLiveData<>();

private MutableLiveData<TokenResponse> tokenResponse;
private Application application;

public LoginViewModel(@NonNull Application application) {
    super(application);
    this.application = application;
}

public boolean isValidData() {
    boolean isValid = true;

    Log.d("fekjfnew","email = "+userName.getValue()+",, pass = "+password.getValue());

    if (userName.getValue() == null || userName.getValue().equals("")) {

        userNameError.setValue("Invalid Email");
        isValid = false;
        userNameErrorVisibility.setValue(true);

    } else {
        userNameError.setValue(null);
        userNameErrorVisibility.setValue(false);
    }

    if (password.getValue() == null || password.getValue().equals("")) {
        passwordError.setValue("Password too short");
        passwordErrorVisibility.setValue(true);
        isValid = false;

    } else {
        passwordError.setValue(null);
        passwordErrorVisibility.setValue(false);
    }

    return isValid;
}


public MutableLiveData<TokenResponse> getAuthenticateTokenData() {
    tokenResponse = new MutableLiveData<>();
    if(isValidData()) {
    // Call Repository to Perform API operation
    }
    return tokenResponse;
}





public void setPasswordIcon(boolean isVisible) {
    isViewPasswordIconVisible.setValue(isVisible);
}

public void resetEditTextField(String filedName) {

    if(filedName.equals("username"))
        userNameErrorVisibility.setValue(false);
    else if(filedName.equals("password"))
        passwordErrorVisibility.setValue(false);
}
}

activity_login_new.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="com.test.views.activities.LoginActivityNew">

<data>
    <import type="android.view.View"/>
    <variable name="viewModel" type="com.test.viewModels.LoginViewModel"/>

</data>

<LinearLayout
    android:padding="40dp"
    android:orientation="vertical"
    android:id="@+id/cl_login"
    android:gravity="center_horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#4">


    <android.support.v7.widget.AppCompatTextView
        android:id="@+id/tv_sign_in"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/text_sign_in"
        android:textColor="@color/colorWhite"
        android:textSize="@dimen/login_header_text_size"
        android:layout_marginTop="50dp"
        />

    <android.support.v7.widget.AppCompatEditText
        android:id="@+id/et_user_name"
        android:layout_width="match_parent"
        style="@style/LoginEditTextViewStyle"
        android:layout_marginTop="10dp"
        android:background="@{viewModel.userNameErrorVisibility ? @drawable/bg_error_edit_text : @drawable/bg_edit_text}"
        android:ems="10"
        android:hint="@string/hint_username_email"
        android:imeOptions="actionNext"
        android:transitionName=""
        android:inputType="textPersonName"
        android:paddingStart="20dp"
        android:paddingTop="10dp"
        android:paddingEnd="20dp"
        android:text="@={viewModel.userName}"
        android:paddingBottom="10dp"
        android:layout_height="@dimen/login_height_of_edit_text" />

    <android.support.v7.widget.AppCompatTextView
        android:id="@+id/tv_incorrect_username"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:layout_marginTop="10dp"
        android:text="@={viewModel.userNameError}"
        android:textColor="@color/colorErrorText"
        android:textSize="@dimen/wrong_entries_text_size"
        android:visibility="@{viewModel.userNameErrorVisibility ? View.VISIBLE : View.GONE}"
      />

    <android.support.design.widget.TextInputEditText
        android:id="@+id/et_password"
        android:layout_width="match_parent"
        style="@style/LoginEditTextViewStyle"
        android:layout_marginTop="30dp"
        android:background="@{viewModel.passwordErrorVisibility ? @drawable/bg_error_edit_text : @drawable/bg_edit_text}"
        android:ems="10"
        android:text="@={viewModel.password}"
        android:hint="@string/hint_password"
        android:imeOptions="actionDone"
        android:inputType="text"
        android:paddingStart="20dp"
        android:paddingTop="10dp"
        android:paddingEnd="20dp"
        android:paddingBottom="10dp"
        android:layout_height="@dimen/login_height_of_edit_text" />


    <android.support.v7.widget.AppCompatTextView
        android:id="@+id/tv_incorrect_password"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:layout_marginTop="10dp"
        android:text="@={viewModel.passwordError}"
        android:textColor="@color/colorErrorText"
        android:textSize="@dimen/wrong_entries_text_size"
        android:visibility="@{viewModel.passwordErrorVisibility ? View.VISIBLE : View.GONE}"
        app:layout_constraintStart_toEndOf="@id/guideline_v1"
        app:layout_constraintTop_toBottomOf="@id/et_password" />

    <android.support.v7.widget.AppCompatButton
        android:id="@+id/button_login"
        android:layout_width="match_parent"
        android:layout_marginBottom="20dp"
        android:background="#FF077DB2"
        android:text="@string/label_sign_in"
        android:textAllCaps="false"
        android:layout_height="@dimen/login_height_of_edit_text"
        android:textColor="#ffffff" />

    <LinearLayout
        android:id="@+id/ll_finger_print"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:gravity="center"
        android:orientation="horizontal"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        android:visibility="gone"
        app:layout_constraintTop_toBottomOf="@id/button_login">

        <android.support.v7.widget.AppCompatImageView
            android:layout_width="24dp"
            android:layout_height="24dp"
            android:src="@drawable/ic_fingerprint" />

        <android.support.v7.widget.AppCompatTextView
            android:id="@+id/text_fingerprint"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:text="@string/text_fingerprint_id"
            android:textColor="@color/colorWhite"
            android:textSize="@dimen/fingerprint_id_text_size"
            app:layout_constraintStart_toEndOf="@id/guideline_v7"
            app:layout_constraintTop_toBottomOf="@id/button_login" />
    </LinearLayout>
</LinearLayout>

样式.xml
<style name="LoginEditTextViewStyle" parent="android:Theme">
    <item name="android:paddingStart">20dp</item>
    <item name="android:paddingEnd">20dp</item>
    <item name="android:paddingTop">10dp</item>
    <item name="android:paddingBottom">10dp</item>
    <item name="android:textColor">@color/colorWhite</item>
    <item name="android:textColorHint">@color/colorWhiteWithThirtyTransparency</item>
    <item name="android:background">@drawable/bg_edit_text</item>
    <item name="android:textSize">@dimen/login_edit_text_size</item>
</style>

最佳答案

首先,您的点击监听器的代码包含应用程序逻辑,不应该在 View 中,而是在 View 模型中(例如,您可以在 View 模型中添加一个名为 login() 的公共(public)方法并处理其中的登录逻辑) .

其次,为了将点击事件绑定(bind)到方法,您可以在布局的 XML 文件中执行此操作:

<android.support.v7.widget.AppCompatButton
    android:id="@+id/button_login"
    ...
    android:onClick="@{() -> viewModel.login()}" />

然后,在单元测试中,您可以调用方法 login()为了测试它。

另一方面,绑定(bind)在 XML 中不直接可用的回调,例如 OnTouch ,您可以创建适配器以使其可用:
object MyAdapters {

    ...

    @JvmStatic
    @BindingAdapter("onTouch")
    fun setTouchListener(view: View, callback: () -> Boolean) {
        view.setOnTouchListener { v, event -> callback() }
    }
}

<android.support.v7.widget.AppCompatButton
    android:id="@+id/button_login"
    ...
    app:onTouch="@{() -> viewModel.methodThatReturnsABoolean()}" />

请注意,您无法获取 MotionEvent OnTouchListener 的值使用上面显示的代码。如果你需要它,那么你将不得不以不同的方式实现你的适配器:
object MyAdapters {

    ...

    @JvmStatic
    @BindingAdapter("onTouchListener")
    fun setTouchListener(view: View, listener: OnTouchListener) {
        view.setOnTouchListener(listener)
    }
}

<android.support.v7.widget.AppCompatButton
    android:id="@+id/button_login"
    ...
    app:onTouchListener="@{viewModel.onTouchListener}" />

关于android - 如何在 MVVM Android 中使用数据绑定(bind)处理 ViewModel 中的 onClick 或 onTouch 类事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55926038/

相关文章:

android - 如何获取wifi接入点和移动设备之间的距离

java - 为什么会出现这个错误我什至还没有开始编码

android - Admob 实现错误

asp.net-mvc - ASP .NET 和 Silverlight UI 的架构模式

javascript - 在 ExtJS MVVM 中显示网格中的存储值

android - 自定义 View 上的自定义监听器的数据绑定(bind)

android - 我可以将 View 作为 Android 上数据绑定(bind) BindingAdapter 的输入吗?

android - 在屏幕上滑动手指激活按钮,就好像它们被按下一样

c# - WPF 数据网格允许用户添加行?

android - 未解析的引用 : databinding in Android studio 4. 1.3