RealmRecylerAdapter with RxJava - android

With the following code. I expect:
1. Get all the data from the server side.
2. Insert all the data to Realm
3. Refresh RecycleView with Realm Adapter.
The first time, the data is always empty. Maybe the data is still not ready but subscribe is still invoked? Is there any way to update the view when the data is ready?
ApiCall().doOnNext(response -> {
Realm realm = Realm.getDefaultInstance();
try {
realm.beginTransaction();
realm.insertOrUpdate(response);
realm.commitTransaction();
} finally {
realm.close();
}
}).subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new exObserver<List<T>>() {
#Override
public void onResponse(List<T> response) {
updateView()
}
#Override
public void onFailure(exException exception) {
adapter.notifyChanged();
}
});

As EpicPandaForce mentioned, you don't need to notify RealmRecyclerViewAdapter when there is a change.
My answer will specifically target your use of RxJava.
doOnNext() is a side-effect method.
This means it will be called parallel to your stream, without affecting it.
When your api call returns, the Action1 in doOnNext() and the Observer in subscribe() will both be triggered at the same time.
This means updateView() is called before the Realm transaction finishes.
If you want to update your view after inserting into the DB, your transaction must happen in your stream.
You could use flatMap() for this purpose.

RealmRecyclerViewAdapter already observes the provided data set with a RealmChangeListener, and calls adapter.notifyDataSetChanged() when a change happens in the underlying Realm.
Why are you trying to manually call adapter.notifyDataSetChanged()? The RealmRecyclerViewAdapter was specifically created so that you don't need to do that.

What is your apiCall() method? try using Observable.defer(()-> apicall()) if you're not using Retrofit. This will delay code evaluation until actual value arrive.

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 MVP - RxJava and retrofit - best approach

I'm figuring out how to develop an Android app, using MVP, RxJava2 and retrofit.
In my presenter, here is the code:
public void loadData() {
compositeDisposable.dataModelRepository.getDataList().subscribeOn(Schedulers.io())
.observeOn(mainScheduler).subscribe(new Consumer<List<Data>>() {
#Override
public void accept(List<Data> dataList) throws Exception {
if (!dataList.isEmpty())
view.displayData(dataList);
else
view.displayEmpty();
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
System.out.println(throwable.toString());
view.displayError("boooom");
}
});
}
Retrofit interface has been defined in the following way:
#GET("/fooURL")
Single<List<Data>> getDataList();
And the repository is just
public Single<List<Data>> getDataList() {
return retrofitApi.getDataList();
}
And it is working fine. Question is as follows: my intention is to fetch network data only when data is not available locally, in db.
Having this in mind, is it correct that schedulers are managed in the presenter? Or should they be managed in the Repository?
My guess is that presenter is the correct place, as it creates a thread so repository can do its stuff sequentially (fetch db, if nothing, then fetch network/cache; return data wherever it has been fetched), and when data is provided, notify the view inside the accept method of the Consumer.
Is it correct? Or should it be done in a different way?
Another point is: how can i test using Mockito the repository? The dataModelRepository.getDataList() method i mean? Not sure how to do any Assert for Single objects...
Thanks in advance!
I suggest you to offload all business logic that is related to fetching, retrieving data to a central repository.
One way to achieve somewhat similar to what you have described is to use a concat operator.
Observable<List<Data>> getData() {
return Observable
.concat(localRepository.getData(), remoteRepository.getData())
.first();
}
This will try to get data from your local repository first and if it has no data it will make a network request.
I assume your local and remote repositories will be observed on a new thread, but if you need to perform any action on the UI, simply subscribe on a main thread in your presenter.

How to create Observable for async response?

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.

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

Realm with RxAndroid not picking up latest data changes

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.

Categories

Resources