显然,在Android中,我显然没有得到一门科学来进行数据保全。我继续通过片段或视图模型来对抗更新视图,其中某些事情“可以正常工作”,而另一些似乎是无法正常工作的。
我想禁用登录按钮,更改其文本并在单击该按钮后设置Alpha,直到获得API响应,然后再将其更改。可以想象,我不希望用户重复提交身份验证请求。对该片段进行预数据绑定将处理onClick()侦听器,并且我将使用直刀来绑定元素,或者使用findViewById()手动进行绑定。无论哪种方式,当我更改视图时,它几乎都是即时的。现在有了数据绑定,它似乎是偶然的,在某些情况下非常慢,而在另一些情况下却似乎是即时的。我不包括将其更改回原先的代码,因为其一开始并未更改视图。
在写这篇文章时,我一遍又一遍地遇到了同样的问题,按钮从未改变。片段中的观察者被调用(在日志中,它按照我期望被调用的顺序显示),但UI只是不更新。我再次在模拟器上运行它,奇怪的是直到很久以后才出现观察者日志,然后按预期方式更新了视图。当我单击按钮时它没有更新,但是至少在API响应返回之前按钮已更改。这并不是很有用,因为单击和ui更改按钮之间的时间仍然可以快速单击。我停止了该应用程序,然后重新运行它,并立即返回到原始问题,它根本没有更新。测试之间未更改代码。
我使用未经Google架构示例修改的SingleLiveEventhttps://github.com/android/architecture-samples/blob/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/SingleLiveEvent.java
activity_main_login
<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>
<variable
name="mainViewModel"
type="com.example.viewmodel.MainViewModel" />
</data>
...
<Button
android:id="@+id/btnLogin"
android:layout_width="135dp"
android:layout_height="47dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="16dp"
android:onClick="@{() -> mainViewModel.loginClicked()}"
android:text="@string/login"
android:textColor="#ffffff"
android:textStyle="bold"
android:background="#e05206"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fingerprintSwitch"
tools:layout_editor_absoluteX="101dp" />
....
</layout>
MainFragment
ActivityMainLoginBinding binding;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Log.d(TAG, "-> onCreateView()" );
super.onCreateView(inflater, container, savedInstanceState);
mainViewModel = new ViewModelProvider(this).get(MainViewModel.class);
getLifecycle().addObserver(mainViewModel);
binding = DataBindingUtil.inflate(inflater, R.layout.activity_main_login, container, false);
mView = binding.getRoot();
binding.setMainViewModel(mainViewModel);
binding.setLifecycleOwner(this); // Yeah this is what I forgot last time...
return mView;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
AppLog.d(TAG, "-> onViewCreated()" );
super.onViewCreated(view, savedInstanceState);
mainViewModel.getShowLoading().observe(getViewLifecycleOwner(), showLoading -> {
AppLog.d(TAG, "showLoading changed");
this.loading = true;
binding.btnLogin.setText(R.string.loggingIn);
binding.btnLogin.setEnabled(false);
binding.btnLogin.setAlpha(.5f);
});
}
MainViewModel
private SingleLiveEvent<Boolean> showLoading = new SingleLiveEvent<>();
public void loginClicked() {
Log.d(TAG, "loginClicked()");
showLoading.setValue(true);
login();
}
这是日志运行时的样子,您单击登录按钮...
D/MainViewModel: loginClicked()
D/MainFragment: showLoading changed
D/MainViewModel: login()
不是我包括了可以进行/接收API调用的Retrofit2代码,但它与线程有关。我将调用同步改造资源的方法包装在
中new Handler().post(() -> { });
所以login()现在看起来更像这样
private void login() {
new Handler().post(() -> {
// original retrofit call
Thread t = new Thread(() -> authResponse = restApi.doAuthSync());
t.start();
// Joining thread so we wait for the response
// I believe this to be the actual culprit of the problem
try {
t.join();
} catch (InterruptedException e) {
Log.e(TAG, e.getMessage());
}
// handle authResponse
...
});
}
尽管API调用本身必须在其自己的线程上完成(并且以前)是由于thread.join()使整个事情最可能阻碍了UI线程。这导致数据绑定不更新。使用RxJava可能已经解决了这个问题,但是我还没有实现它,对于简单的任务,它不是必需的。