Rxjava and work manager chained asychronous calls - android

Update: This was how my old insertIntoDb method looked like which didn't work :
private Completable insertIntoDb(List<ArticleEntity> articleItemEntities) {
return database.articleDao().insertArticles(articleItemEntities)
.observeOn(Schedulers.io());
}
I changed it to the following and now it works :
private void insertIntoDbNew(List<ArticleEntity> articleItemEntities) {
mCompositeDisposable.add(
database.articleDao().insertArticles(articleItemEntities)
.subscribeOn(Schedulers.io())
.subscribe());
}
I don't know why but now it works. Sure the worker completes before the database insert completes but that doesn't seem to be a problem which I believed before.
End of update.
I'm new to reactive programming. My goal is to schedule a work manager to do 4 Actions then return a result using RxJava2. Here are the tasks I want to perform
Do a web api call.
Structure the data we get from the API call.
Insert it into our local room database.
When everything is complete signal Result.success() back to the Job so it knows that everything went ok and can terminate.
So my preferred method would look something like this.
public Result doWork(){
return api.get("URL") responseData -> structureData(responseData) structuredData -> insertIntoDB(structuredData) -> Result.success()
}
I'm using RxJava2 and the RxWorker class.
Below is my current solution. Is this correct or am I doing something wrong?
public class DownloadWorker extends RxWorker {
#Override
public Single<Result> createWork() {
return apiService.download("URL")
.map(response -> processResponse(response))
.doOnSuccess(data -> insertIntoDb(data))
.flatMap(response ->
allComplete()
)
.observeOn(Schedulers.io());
}
Single<Result> allComplete() {
return Single.just(Result.success());
}
}
It behaves like I want it to. It downloads the data, structures it, then inserts it into the DB then returns Result.success(). But I have no idea what I am doing. Am I using RxJava as it was intended?
Also this part bothers me :
.flatMap(response -> allComplete())
the response part is superfluous can I remove it somehow?

I did some improvements to your code:
public class DownloadWorker extends RxWorker {
#Override
public Single<Result> createWork() {
return apiService.download("URL")
.map(response -> processResponse(response))
.flatMapCompletable(articleItemEntities -> database.articleDao().insertArticles(articleItemEntities))
.toSingleDefault(Result.success())
.onErrorReturnItem(Result.failure())
.observeOn(Schedulers.io());
}
}
In original code, you save data using doOnSuccess method which is a side effect. As you mentioned in your comment, insertIntoDb() method returns Completable. Therefore, I changed doOnSuccess(data -> insertIntoDb(data)) to flatMapCompletable(data -> insertIntoDb(data)) which will allow you make sure storing data succeeded and wait until it finishes. As insertIntoDb() method returns Completable and createWork() method has to return Result, we have to now change type from Completable to Single<Result>. Therefore, I used toSingleDefault which returns Result.success() by default. Also, I added onErrorReturnItem(Result.failure()) which will allow RxWorker to track errors. I hope my answer helps.

Related

Problems returning LiveData in Activity that observes changes in database using MVVM model

In my application I return LiveData from Room database (SQLite) in repository, and observe the data on my application Activity.
The problem is: having LiveData in Activity that observes changes in database using MVVM model, and runs some code when data is changed (as this is how observe works).
The method looks like this in repository:
public LiveData<TourWithAllGeoPoints> getTourWithAllGeoPoints(long tourId, boolean mIsFirstTime) {
if (!mIsFirstTime) {
return tourWithAllGeoPoints;
}
MyTourAssistentDatabase.databaseWriteExecutor.execute(()-> {
tourWithAllGeoPoints = toursDAO.getTourWithAllGeoPoints(tourId); //this part finishes after reuturn
});
return tourWithAllGeoPoints; //this part returns
}
mIsFirstTime checks if the Activity (or Fragment) is loading first time or not (if Bundle is null or not).
databaseWriteExecutor.execute() is a ThreadPool executing the code in own thread.
toursDAO.getTourWithAllGeoPoints(tourId) is where I ask and get data from Room database. It returns a LiveData object.
In Activity code I do observe the LiveData:
activeTourViewModel.getTourWithAllGeoPoints(tourId, mIsFirstTime).observe(this, geoPointsPlanned -> {
//Some code here changing UI, other variables, etc.
}
But the problem is that the method returns 'tourWithAllGeoPoints' before the execute() part is finished. So this means it returns an empty LiveData. Or the LiveData we observe on MainActivity is not the same LiveData we get from toursDAO.
And so in Activity it observes the empty LiveData.
My attempted solutions are:
1) I can run the query in main thread like this:
public LiveData<TourWithAllGeoPoints> getTourWithAllGeoPoints(long tourId, boolean mIsFirstTime) {
if (!mIsFirstTime) {
return tourWithAllGeoPoints;
}
tourWithAllGeoPoints = toursDAO.getTourWithAllGeoPoints(tourId);
return tourWithAllGeoPoints;
}
But then it gives warning message about not to run queries to Room database on main thread as it may take long time.
2) Or I can make the toursDAO.getTourWithAllGeoPoints(tourId) return a TourWithAllGeoPoints object rather than a LiveData, and put it into a LiveDataobject, like this:
public LiveData<TourWithAllGeoPoints> getTourWithAllGeoPoints(long tourId, boolean mIsFirstTime) {
if (!mIsFirstTime) {
return tourWithAllGeoPoints;
}
MyTourAssistentDatabase.databaseWriteExecutor.execute(()-> {
TourWithAllGeoPoints twagp = toursDAO.getTourWithAllGeoPoints(tourId);
tourWithAllGeoPoints.postValue(twagp)
});
return tourWithAllGeoPoints;
}
So that it observes the changes in LiveData. But then I can't observe the changes made in database, since it just returns a List. This means I have to run the same method every time I make a change in the database.
3) Or I can put a LiveData inside a LiveData, also like this:
public LiveData<LiveData<TourWithAllGeoPoints>> getTourWithAllGeoPoints(long tourId, boolean mIsFirstTime) {
if (!mIsFirstTime) {
return tourWithAllGeoPoints;
}
MyTourAssistentDatabase.databaseWriteExecutor.execute(()-> {
LiveData<TourWithAllGeoPoints> twagp = toursDAO.getTourWithAllGeoPoints(tourId); //returns LiveData
tourWithAllGeoPoints.postValue(twagp)
});
return tourWithAllGeoPoints;
}
But I don't know if putting LiveData inside a LiveData is a good idea or not.
Or the are other solutions. But how can I solve this problem?
The problem is: having LiveData in Activity that observes changes in database using MVVM model, and runs some code when data is changed (as this is how observe works).
For the specific problem you described (i.e. returning the first TourWithAllGeoPoints and nothing else), it seems LiveData isn't the most appropriate data type you can use here. LiveData is meant to be used when, as the name says, the underlying data is live and it could change anytime, and you need to observe the data everytime it changes. If all you need is one value, it's better not to use LiveData at all. Just make your DAO method getTourWithAllGeoPoints return TourWithAllGeoPoints (without LiveData) and call it from a background thread. Take a look at this link for some ways to do that. It's much easier to use Kotlin coroutines in this case, but you'd need to be using Kotlin for that (which I recommend :) ).
But if the problem you described is generic (not exactly just for returning one value once), you can use a MediatorLiveData to observe a LiveData and post something different (or not) every time it emits a new value. Take a look at this code:
private MediatorLiveData<TourWithAllGeoPoints> mediator;
public YourRepositoryConstructor() {
mediator = new MediatorLiveData<>();
mediator.addSource(toursDAO.getTourWithAllGeoPoints(tourId), data -> {
if (mediator.getValue() != null) {
mediator.setValue(data);
}
});
return mediator;
}
public LiveData<TourWithAllGeoPoints> getTourWithAllGeoPoints(long tourId, boolean mIsFirstTime) {
return mediator;
}
A MediatorLiveData observes one (or many) other LiveData objects and emits a new value according to the changes of the other LiveData objects. It may emit a different data type (i.e. it doesn't have to be the same type of the underlying LiveData objects) or even not emit anything at all. It's all according to your MediatorLiveData code. In this case specifically, every time the result of getTourWithAllGeoPoints emits something new, you MediatorLiveData will react to that and only emit a new value in itself if it's the first time. It can do that by checking if it's value is null, it doesn't need the variable mIsFirstTime (unless null is a valid value for you in that case).
The MediatorLiveData is a more generic approach suitable for the type of scenario you described, but it may be too much effort if you only need one result for that query, which you might solve by not using LiveData at all.

Android and RxJava: insert into Room database a Retrofit Response

I have two slightly different Question classes. One is an retrofit call results object, and the other is a Room #Entity in my Android App.
And now I want from my Interactor class (Use-case) class do the following:
Make a call to the API and result (List where question is
the Retrofit response class)
On success, make a new Game object in my Room database. This operation have long (#Entity id which is autogenerated) as return
type.
for each Question from retrofit response (from (1)), question -> Converter which converts from retrofit.Question to
database.Question. Converter method takes 2 parameters, the
retrofit.Question object and the ID which was returned in step (2).
After conversion, add to database.
Observe on AndroidSchedulers.mainthread. (subscribeOn is called from repository)
Now the problem I am having is creating this stream with RxJava from my Interactor class.
Here is all the classes and calls. First is my Interactor.class method which should do the stream described above:
public Single<List<Question>> getQuestionByCategoryMultiple(String parameter);
The API CALL from MyAPI.class:
//this Question is of database.Question.
Single<List<Question>> getQuestionByCategory(String parameter);
The Room database repository.class:
Single<Long> addGameReturnId(Game game);
Completable addQuestions(List<Question> questions);
Converter.class:
public static List<database.Question> toDatabase(List<retrofit.Question> toConvert, int id);
I am having trouble creating the stream described above with these methods. I tried a mix of .flatmap, .zip, .doOnSuccess, etc without successfully creating the stream.
If there is anything else you need me to explain, or explain the problem better, please comment below.
public Single> getQuestionByCategoryMultiple(String parameters){
return openTDBType
.getQuestionByCategory(paramters) //step 1
// step 2
// step 3
.observeOn(AndroidSchedulers.mainThread()); //step 4
}
EDIT:
I tried something like this:
return openTDBType
.getQuestionByCategory(parameters)
.map(QuestionConverter::toDatabase)
.flatMap(questions -> {
int id = gameRepositoryType.addGameReturnId(new Game(parameters).blockingGet().intValue();
questions.forEach(question -> question.setqId(id));
gameRepositoryType.addQuestions(questions);
return gameRepositoryType.getAllQuestions(); })
.observeOn(AndroidSchedulers.mainThread());
^^ I don't know if this is the best way to go about this one? Can anyone confirm if this is a good way to design what I want to do here, or if there are better ways or any suggestions?
Try not use blockingGet especially when it is avoidable. Also, addQuestions won't be executed at all because it is not subscribed. You can add both addGameReturnId and addQuestions into the chain like this:
return openTDBType
.getQuestionByCategory(parameters)
.map(QuestionConverter::toDatabase)
.flatMap(questions -> {
return gameRepositoryType.addGameReturnId(new Game(parameters)) // returns Single<Long>
.map(id -> {
questions.forEach(question -> question.setqId(id));
return questions;
})
}) // returns Single<List<Question>> with the GameId attached
.flatMapCompletable(questions -> gameRepositoryType.addQuestions(questions)) // returns Completable
.andThen(gameRepositoryType.getAllQuestions()) // returns Single<>
.observeOn(AndroidSchedulers.mainThread());

RxJava observe multiple observers in single subscriber

Code
Author author = baseRealm.where(Author.class).equalTo("id", mId).findFirst();
public boolean checkGlobalSyncStatus(Author author, List<Books> mBooks) {
final boolean[] isJobSynchronized = {false};
Observable.fromIterable(mBooks)
.filter(Books::isChanged)
.doOnNext(book -> isJobSynchronized[0] = true)
.just(author)
.flatMapIterable(Author::getAllBooks)
.filter(MyBook::isChanged)
.doOnNext(mBook -> isJobSynchronized[0] = true)
.just(author)
.flatMapIterable(Author::getAllWriters)
.filter(Writers::isChanged)
.doOnNext(jobPage -> isJobSynchronized[0] = true)
.subscribe();
return isJobSynchronized[0];
}
Problem
fromIterable(mBooks) is called from static-reference Observable BUT just(author) is called from instance-reference.
I only want to get this operation done in single query. I can make different observable for each and perform desired operation but that would be lengthy.
Why?
By doing so, SonarQube is giving me unsuccessful check and forcing me to remove instance-reference.
Any alternatives will be appreciated.
You are trying to use just() as an operator when it is really an observable. It looks like your intention is to use the passed in author to make a series of queries, and then check that any of the books associated with the author have "changed".
Additionally, you are trying to return a boolean value that likely has not been set by the time the return occurs. You may need to block and wait for the observer chain to finish if you want the value. More likely, you want the observer chain to finish if any book has changed.
Additionally, the series of steps where you set the flag to true come down to setting the flag to true the first time.
Instead of just(), use map() to rebind the original author into the observer chain. Use the toBlocking() operator to make the process synchronous.
Observable.fromIterable(mBooks)
.filter(Books::isChanged)
.toBlocking()
.subscribe( ignored -> isJobSynchronized[0] = true );
return isJobSynchronized[0];
Since the (presumably) asynchronous queries are no longer necessary to compute the value, remove RxJava:
return mBooks.stream()
.filter(Books::isChanged)
.anyMatch();
There is no reason to use RxJava here, however, the proper combination of operators would be as follows:
Author author = baseRealm.where(Author.class).equalTo("id", mId).findFirst();
public boolean checkGlobalSyncStatus(Author author, List<Books> mBooks) {
return Single.concat(
Observable.fromIterable(mBooks)
.any(Books::isChanged)
, // ---------------------------------------------
Observable.fromIterable(author.getAllBooks)
.any(MyBook::isChanged)
, // ---------------------------------------------
Observable.fromIterable(author.getAllWriters)
.any(Writers::isChanged)
)
.any(bool -> bool)
.blockingGet();
}

How to verify no interactions on RxJava2 Observable using Mockito

Observable is final class, so we can't create mocks for it to verify or capture any interactions performed on it.
And Observable's test(), TestSubscriber also does not provide any such interaction assertion techinque
I have created a generic method for checking cached data before loading from network
/**
* General parametrized method for loading data from service while checking connection
* and respecting reload state
*/
private <T> Observable<T> getData(Observable<T> dataSource, String key, boolean reload) {
T cachedData = (T) cacheModel.getValue(key);
// Do not use cache if reloading
if(!reload && cachedData != null)
return Observable.just(cachedData);
if(!utilModel.isConnected()) {
return Observable.error(new Throwable(Constants.NO_NETWORK));
}
return dataSource
.doOnNext(data -> cacheModel.saveObject(key, data))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
And using it as follows:
#Override
public Observable<User> getOrganiser(boolean reload) {
return getData(eventService.getUser(authorization), ORGANIZER, reload);
}
Before, I was not even calling eventService.getUser(...) to get the observable, so I could test if it was never being called, but now, as I have to pass it to the template method, I need to call it but verify if cache is present, it is never interacted with.
This is my previous test, which is obviously failing now
#Test
public void shouldLoadOrganizerFromCache() {
// Clear cache
objectCache.clear();
User user = new User();
objectCache.saveObject(RetrofitEventRepository.ORGANIZER, user);
// No force reload ensures use of cache
Observable<User> userObservable = retrofitEventModel.getOrganiser(false);
userObservable.test().assertNoErrors();
userObservable.test().assertValue(user);
verify(eventService, never()).getUser(auth);
}
Assuming the Observable you want to test is something like this:
Observable<User> userObservable = getData(eventService.getUser(authorization));
while you can't use directly test() method or subscribing with TestObserver, as you hand the input Observable to a different entity. you can use Observable side effect methods to verify almost any interaction with the Observable.
for instance in your case, you can use doOnSubscribe and raise a flag if it was called (indicates the service method was called while it shouldn't as cache should be invoked):
final boolean[] serviceCalled = new boolean[1];
Observable<User> userServiceObservable = eventService.getUser(authorization)
.doOnSubscribe(disposable -> serviceCalled[0] = true);
Observable<User> userObservable = getData(userServiceObservable, ORGANIZER, reload);
userObservable.test()
.assertNoErrors()
.assertValue(user)
.awaitTerminalEvent();
Assert.assertEquals(false, serviceCalled[0]);
BTW, your cache method might not work as expected as your'e testing the cache at getting the Observable and not when subscribing, so cache state can be different at subscribe time. also multiple calls can happen simultaneously resulting with multiple calls to server and updating the cache, you can see here my suggestion of cache using Observable.defer().

Chaining requests in Retrofit + RxJava

I have 2 APIs that I want to make request to in sequence and store their data in SQLite.
First I want to make request to API A and store its data in SQL table a. Then make request to API B and store its data in table b and some data in table a_b. The data stored in a_b is from request B alone.
How can I do this using RxJava. I read somewhere about using flatMap for this, something like this
apiService.A()
// store in DB here? How? maybe use map()?
.flatMap(modelA -> {
// or maybe store modelA in DB here?
return apiService.B().map(modelB -> {
storeInDB()l // store B here ?
return modelB;
});
});
If I wasn't using lambda functions, this would look as ugly as normal nested calls. Is this a better way to do it?
I don't think using map operator is the best way to go with things like storing the result of the api call.
What I like to do is to separate those things inside doOnNext operators. So your example would be something like this:
apiService.A()
.doOnNext(modelA -> db.store(modelA))
.flatMap(modelA -> apiService.B())
.doOnNext(modelB -> db.store(modelB));
(add necessary observeOn and subscribeOn yourself, exactly like you need them)
Yes, you can use flatmap for this exact purpose. See the below example (Assuming your service A returns Observable<FooA> and service B returns Observable<FooB>)
api.serviceA()
.flatMap(new Func1<FooA, Observable<FooB>>() {
#Override
public Observable<FooB> call(FooA fooA) {
// code to save data from service A to db
// call service B
return api.serviceB();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<FooB>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(FooB fooB) {
// code to save data from service B to db
}
});

Categories

Resources