Greggz
Greggz

Reputation: 1799

LiveData not triggering twice in case of network request failing

My LiveData object is called twice when my httprequest is succefull, but in case of an error, it's only called once, causing the UI to display an empty list since my Error verification was skipped since there was no alert fired.

My repository code

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();
}

My BaseRepository code

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

My Fragment code

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);
        }
    });
}

And finally, my ViewModel code

public LiveData<DataWrapper<List<Card>>> getCards(){
    return repository.loadCards();
}

To summarize, in case of failure, why does the observer callback only gets called once ? Because I've debug it, and in both ( succefull and failure ) cases, the method asLiveData is called TWICE, but it's only in the succefull attempt that the callback is called TWICE aswell, on failure the observer callback it's only called ONCE.

Edit: Added asynctask code

@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;
    }
};

Upvotes: 0

Views: 669

Answers (1)

Chris
Chris

Reputation: 2362

The reason you get 2 callbacks for the success case and only 1 call for the error scenario looks to be to do with your repository setup.

When calling loadCards you first emit the state of the database with this call:

atomicWrapper.set(asLiveData(getCards(),null));

Your database will be queried at this point and the current values will trigger the mediatorLiveData.setValue. This will be the first emission.

mediatorLiveData.addSource(dataSource, data -> mediatorLiveData.setValue(new DataWrapper<>(data, error)));

At the same time you have triggered a call to you webservice, which if successful will trigger your asynctask to update the database.

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

Once the insert command completes the MediatorLiveData will trigger it's setValue call again - it's listening to changes in the database so on the insertion it will receive a callback. This is the second emission in the success case.

In the error scenario you pass through null as the dataSource. Its surprising this doesn't crash as the addSource method marks the source parameter as Non Null. As it is null you don't get a callback and the mediatorLiveData.setValue won't be called. This means for an error scenario you only receive the first emission.

It may be simpler if you did something like the following:

  • setup a listener on the database and emit your datavalue without the error when a database update occurs.

  • on receiving an error you could then just emit the DataWrapper with an error

e.g. something like:

    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);
        }
    }

Upvotes: 1

Related Questions