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());
Related
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.
The DarkSkyAPI call returns Forecast object containing WeeklyData object that in turn contains Array<DailyData>.
My Repository class requires Array<DailyData> to cache and propagate the data to the Presenter.
Currently I am calling the API like this:
Flowable<Forecast> response = service.getRxWeatherResponse(params...);.
How can I unwrap this Flowable<Forecast> to extract Flowable<Array<DailyData>> to be returned to the Repository class?
Thank you.
Got it Chris, thanks! I've used the map operator as you advised. Final code returns Observable and looks like this:
return service.getRxWeatherResponse(API cal params...)
.map(new Function<Forecast, List<DailyData>>() {
#Override
public List<DailyData> apply(Forecast forecast) throws Exception {
return forecast.getWeeklyData().getDailyData();
}
});
Or simplified using lambda:
return service.getRxWeatherResponse(API cal params...)
.map(forecast -> forecast.getWeeklyData().getDailyDataArray());
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();
}
I have one async-method, like this:
void getPlaceInfo(Place place, PlaceCallback callback)
For example my PlaceCallback has one method:
void success(InfoPlace place);
I want create Observable for waiting response from two requests:
getPlaceInfo(...); // first call
getPlaceInfo(...); // second call
And then I want to get both response at the same time.
Can I make it?
So you need to combine 2 callbacks to evaluate a function like:
Response computeResponse(InfoPlace infoPlace, InfoPlace infoPlace2) {
...
}
And you want to use Rx for this. There is two problem here; wrapping the callback method into Observable, and combine them.
You can't just instantiate an Observable and call myObservable.send(value) or myObservable.next(value). The first solution is to use a Subject. Another solution (the one below) is to create the observable with Observable.create(). You call the callback method and create the listener inside the method Observable.create(), because it's inside Observable.create() that you can call onSuccess() method, the method who told the Observable to pass down a value:
Single<InfoPlace> getInfoPlaceSingle(Place place) {
return Single.create(e -> getPlaceInfo(place, new PlaceCallback() {
#Override
public void success(InfoPlace infoPlace) {
e.onSuccess(infoPlace);
}
}));
}
Or with Java 8:
Single<InfoPlace> getInfoPlaceSingle(Place place) {
return Single.create(e -> getPlaceInfo(place, e::onSuccess));
}
Note, I used Single over Observable, since you await only one value. The interest is a more expressive contract for your function.
Wrapping things into Observable is a pain, but now you are in the reactive realm where Rx provide you a complete toolbox to deal with them. The second part - combining the observables - is easy:
Single.zip(getInfoPlaceSingle(place1), getInfoPlaceSingle(place2), this::computeResponse);
Wrap your async calls using Observable.fromEmitter() and you can then use Observable.zip() to combine the calls.
I am using Realm with RxAndroid. i am having this strange issue where realm is not picking up the latest modification done on DB.
There are 2 methods that i am using.
Observable<Integer> save(Bitmap bitmap).
Observable<Integer> getImageList(Context applicationContext).
Like this
Activity 1
getImageList(applicationContext)
button click -> Activity 2
save(bitmap)
finish()
getImageList(applicationContext)
This method "save" basically adds a newly created model into RealmList.
private Observable<Integer> save(Bitmap bitmap) {
return Observable.create((Observable.OnSubscribe<Integer>) subscriber -> {
--------------------------------------
-----Various file creation stuff------
--------------------------------------
UserImagesModel model = realm
.where(UserImagesModel.class)
.findFirst();
//ImageModel class extends RealmObject
ImageModel imageModel = new ImageModel();
realm.beginTransaction();
//realm object must be Edited inside transaction
model.getResponse().add(0, imageModel);
realm.commitTransaction();
realm.close();
subscriber.onNext(1);
subscriber.onCompleted();
}
}
Ans this method fetches saved list.
public Observable<Integer> getImageList(Context applicationContext) {
return Observable.create((Observable.OnSubscribe<Integer>) subscriber -> {
AppUtils.logD("User image observable instance " + this);
UserImagesModel model;
Realm realm = Realm.getInstance(applicationContext);
model = realm.where(UserImagesModel.class).findFirst();
^
This model doesn't replicate data added in save call
------------------------------------------------
----Various validation and service calls.-------
------------------------------------------------
subscriber.onCompleted();
realm.close();
});
}
}
As i mentioned in code, UserImageModel that i get from Realm doesn't replicate changes i made in save method.
the problem occurs when i call getImageList method second time. also when i print this.toString inside Observable.create it prints same object that was returned first time.
So i believe this issue seems to be with the way i am using RxAndroid. can anyone tell me what i am missing? and how can i resolve it?
UPDATE :
After few tests i realized that this.toString inside Observable.create is actually points to parent object as i have used lamda expression so that is not seems to be the issue and now i am back to square one ;(
Turns out, this is expected behavior of Realm. as i was subscribing those observables on IO threads which doesn't have Looper.
Op here has similar issue. answer explains the case.