在网络请求失败的情况下,LiveData 不会触发两次
LiveData not triggering twice in case of network request failing
我的 LiveData
对象在我的 httprequest 成功时被调用了两次,但如果出现错误,它只会被调用一次,导致 UI 自从我的 [=16] 显示一个空列表=] 验证被跳过,因为没有触发警报。
我的存储库代码
private LiveData<List<Card>> getCards() {
return cardsDao.getCards();
}
// Network request, that deals with the success or error of the request
public LiveData<DataWrapper<List<Card>>> loadCards() {
AtomicReference<LiveData<DataWrapper<List<Card>>>> atomicWrapper = new AtomicReference<>();
if (getCards().getValue() == null) {
webService.getCards(result -> {
CardResponse res = (CardResponse) result;
List<Card> cardList = res.getCardsList();
getInsertAsyncTask.execute(cardList.toArray(new Card[cardList.size()]));
}, error -> {
atomicWrapper.set(asLiveData(null, error.getMessage()));
}, TAG);
}
atomicWrapper.set(asLiveData(getCards(),null));
return atomicWrapper.get();
}
我的 BaseRepository 代码
LiveData<DataWrapper<List<T>>> asLiveData(LiveData<List<T>> dataSource, String error) {
MediatorLiveData<DataWrapper<List<T>>> mediatorLiveData = new MediatorLiveData<>();
mediatorLiveData.addSource(dataSource, data -> mediatorLiveData.setValue(new DataWrapper<>(data, error)));
return mediatorLiveData;
}
我的片段代码
private void subscribeToCards() {
mViewModel.getCards().observe(this, listDataWrapper -> {
if( listDataWrapper == null ) {
return;
}
if( listDataWrapper.error != null) {
// Show error on UI
dismissProgress();
Log.e(TAG, "Error - " + listDataWrapper.error);
showError(getString(R.string.cards_error_get_list_message));
EventBus.getDefault().post(new DialogMessageEvent(getString(R.string.cards_error_get_list_title),
getString(R.string.cards_error_get_list_message), getString(R.string.cards_error_get_list_button)));
}
if( listDataWrapper.data != null ) {
// Update Ui
refreshCardsList(listDataWrapper.data);
cardsViewVisibility(true);
}
});
}
最后,我的 ViewModel 代码
public LiveData<DataWrapper<List<Card>>> getCards(){
return repository.loadCards();
}
总而言之,如果失败,为什么 observer callback
只被调用一次?因为我已经调试了它,并且在两种情况下(成功和失败),方法 asLiveData
都被称为 TWICE,但只有在成功的尝试中回调也被称为 TWICE,失败时 observer callback
只调用一次。
编辑:添加了异步任务代码
@SuppressLint("StaticFieldLeak")
AsyncTask<T,Void,Void> getInsertAsyncTask = new AsyncTask<T, Void, Void>() {
@Override
protected Void doInBackground(T... ts) {
daoObject.insertObjects(Arrays.asList(ts));
return null;
}
};
你得到 2 个成功案例回调和 1 个错误场景调用的原因看起来与你的存储库设置有关。
调用 loadCards 时,您首先会通过此调用发出数据库状态:
atomicWrapper.set(asLiveData(getCards(),null));
此时将查询您的数据库,当前值将触发 mediatorLiveData.setValue。这将是第一次发射。
mediatorLiveData.addSource(dataSource, data -> mediatorLiveData.setValue(new DataWrapper<>(data, error)));
同时您触发了对您的网络服务的调用,如果成功将触发您的 asynctask 更新数据库。
webService.getCards(result -> {
CardResponse res = (CardResponse) result;
List<Card> cardList = res.getCardsList();
getInsertAsyncTask.execute(cardList.toArray(new Card[cardList.size()]));
}, error -> {
atomicWrapper.set(asLiveData(null, error.getMessage()));
}, TAG);
插入命令完成后,MediatorLiveData 将再次触发它的 setValue 调用 - 它正在侦听数据库中的更改,因此在插入时它将收到回调。这是成功案例中的第二次发射。
在错误场景中,您通过 null 作为数据源。令人惊讶的是,这并没有崩溃,因为 addSource 方法将源参数标记为 Non Null。因为它是空的,所以你不会得到回调, mediatorLiveData.setValue 也不会被调用。这意味着对于错误场景,您只会收到第一次发射。
如果您执行以下操作可能会更简单:
在数据库上设置一个侦听器,并在发生数据库更新时发出您的数据值而不会出现错误。
收到错误后,您可以发出带有错误的 DataWrapper
例如类似于:
private final AtomicBoolean isLoading = new AtomicBoolean(false);
private final MediatorLiveData<DataWrapper<List<Card>>> mediatorLiveData = new MediatorLiveData<>();
private MyRepository() {
// requires a cardsDao
// listen for DB changes and emit on callback
mediatorLiveData.addSource(cardsDao.getCards(), data -> mediatorLiveData.setValue(new DataWrapper<>(data, null)));
}
public LiveData<DataWrapper<List<Card>>> cardsData() {
return mediatorLiveData;
}
public void loadCards() {
if (!isLoading.get()) {
isLoading.set(true);
webService.getCards(result -> {
CardResponse res = (CardResponse) result;
List<Card> cardList = res.getCardsList();
// Trigger update in db
getInsertAsyncTask.execute(cardList.toArray(new Card[cardList.size()]));
isLoading.set(false);
}, error -> {
// Emit the error
mediatorLiveData.setValue(new DataWrapper<>(null, error.getMessage()));
isLoading.set(false);
}, TAG);
}
}
我的 LiveData
对象在我的 httprequest 成功时被调用了两次,但如果出现错误,它只会被调用一次,导致 UI 自从我的 [=16] 显示一个空列表=] 验证被跳过,因为没有触发警报。
我的存储库代码
private LiveData<List<Card>> getCards() {
return cardsDao.getCards();
}
// Network request, that deals with the success or error of the request
public LiveData<DataWrapper<List<Card>>> loadCards() {
AtomicReference<LiveData<DataWrapper<List<Card>>>> atomicWrapper = new AtomicReference<>();
if (getCards().getValue() == null) {
webService.getCards(result -> {
CardResponse res = (CardResponse) result;
List<Card> cardList = res.getCardsList();
getInsertAsyncTask.execute(cardList.toArray(new Card[cardList.size()]));
}, error -> {
atomicWrapper.set(asLiveData(null, error.getMessage()));
}, TAG);
}
atomicWrapper.set(asLiveData(getCards(),null));
return atomicWrapper.get();
}
我的 BaseRepository 代码
LiveData<DataWrapper<List<T>>> asLiveData(LiveData<List<T>> dataSource, String error) {
MediatorLiveData<DataWrapper<List<T>>> mediatorLiveData = new MediatorLiveData<>();
mediatorLiveData.addSource(dataSource, data -> mediatorLiveData.setValue(new DataWrapper<>(data, error)));
return mediatorLiveData;
}
我的片段代码
private void subscribeToCards() {
mViewModel.getCards().observe(this, listDataWrapper -> {
if( listDataWrapper == null ) {
return;
}
if( listDataWrapper.error != null) {
// Show error on UI
dismissProgress();
Log.e(TAG, "Error - " + listDataWrapper.error);
showError(getString(R.string.cards_error_get_list_message));
EventBus.getDefault().post(new DialogMessageEvent(getString(R.string.cards_error_get_list_title),
getString(R.string.cards_error_get_list_message), getString(R.string.cards_error_get_list_button)));
}
if( listDataWrapper.data != null ) {
// Update Ui
refreshCardsList(listDataWrapper.data);
cardsViewVisibility(true);
}
});
}
最后,我的 ViewModel 代码
public LiveData<DataWrapper<List<Card>>> getCards(){
return repository.loadCards();
}
总而言之,如果失败,为什么 observer callback
只被调用一次?因为我已经调试了它,并且在两种情况下(成功和失败),方法 asLiveData
都被称为 TWICE,但只有在成功的尝试中回调也被称为 TWICE,失败时 observer callback
只调用一次。
编辑:添加了异步任务代码
@SuppressLint("StaticFieldLeak")
AsyncTask<T,Void,Void> getInsertAsyncTask = new AsyncTask<T, Void, Void>() {
@Override
protected Void doInBackground(T... ts) {
daoObject.insertObjects(Arrays.asList(ts));
return null;
}
};
你得到 2 个成功案例回调和 1 个错误场景调用的原因看起来与你的存储库设置有关。
调用 loadCards 时,您首先会通过此调用发出数据库状态:
atomicWrapper.set(asLiveData(getCards(),null));
此时将查询您的数据库,当前值将触发 mediatorLiveData.setValue。这将是第一次发射。
mediatorLiveData.addSource(dataSource, data -> mediatorLiveData.setValue(new DataWrapper<>(data, error)));
同时您触发了对您的网络服务的调用,如果成功将触发您的 asynctask 更新数据库。
webService.getCards(result -> {
CardResponse res = (CardResponse) result;
List<Card> cardList = res.getCardsList();
getInsertAsyncTask.execute(cardList.toArray(new Card[cardList.size()]));
}, error -> {
atomicWrapper.set(asLiveData(null, error.getMessage()));
}, TAG);
插入命令完成后,MediatorLiveData 将再次触发它的 setValue 调用 - 它正在侦听数据库中的更改,因此在插入时它将收到回调。这是成功案例中的第二次发射。
在错误场景中,您通过 null 作为数据源。令人惊讶的是,这并没有崩溃,因为 addSource 方法将源参数标记为 Non Null。因为它是空的,所以你不会得到回调, mediatorLiveData.setValue 也不会被调用。这意味着对于错误场景,您只会收到第一次发射。
如果您执行以下操作可能会更简单:
在数据库上设置一个侦听器,并在发生数据库更新时发出您的数据值而不会出现错误。
收到错误后,您可以发出带有错误的 DataWrapper
例如类似于:
private final AtomicBoolean isLoading = new AtomicBoolean(false);
private final MediatorLiveData<DataWrapper<List<Card>>> mediatorLiveData = new MediatorLiveData<>();
private MyRepository() {
// requires a cardsDao
// listen for DB changes and emit on callback
mediatorLiveData.addSource(cardsDao.getCards(), data -> mediatorLiveData.setValue(new DataWrapper<>(data, null)));
}
public LiveData<DataWrapper<List<Card>>> cardsData() {
return mediatorLiveData;
}
public void loadCards() {
if (!isLoading.get()) {
isLoading.set(true);
webService.getCards(result -> {
CardResponse res = (CardResponse) result;
List<Card> cardList = res.getCardsList();
// Trigger update in db
getInsertAsyncTask.execute(cardList.toArray(new Card[cardList.size()]));
isLoading.set(false);
}, error -> {
// Emit the error
mediatorLiveData.setValue(new DataWrapper<>(null, error.getMessage()));
isLoading.set(false);
}, TAG);
}
}