我浏览了许多与带有数据绑定的MVVM模型相关的博客。 由于使用ViewModel进行数据绑定使编写junit测试用例变得容易。
我想知道,如何通过ViewModel中的数据绑定实现OnTouchListener
,OnClickListener
,OnFocusChangeListener
之类的侦听器事件,这将使编写单元测试用例变得容易。
我已经使用黄油刀库进行绑定,并且通过执行OnTouch
事件,我的问题是,这是在Activity中实现侦听器而不是在ViewModel中直接实现侦听器的正确方法吗?< / strong>
对于具有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>
styles.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>
答案 0 :(得分:1)
首先,单击侦听器的代码包含应用程序逻辑,不应包含在视图中,而应位于视图模型中(例如,可以在视图模型中添加名为login()
的公共方法并处理其中的登录逻辑)。
第二,为了将click事件绑定到方法,您可以在布局的XML文件中进行操作:
<android.support.v7.widget.AppCompatButton
android:id="@+id/button_login"
...
android:onClick="@{() -> viewModel.login()}" />
然后,在单元测试中,您可以调用方法login()
进行测试。
另一方面,要绑定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
的{{1}}值。如果需要,则必须以不同的方式实现适配器:
OnTouchListener
object MyAdapters {
...
@JvmStatic
@BindingAdapter("onTouchListener")
fun setTouchListener(view: View, listener: OnTouchListener) {
view.setOnTouchListener(listener)
}
}