RxJava + Retrofit + Realm is doing unlimited get request - java

I am completely new to rxJava and it's really confusing, I want to make my app offline first and I've decided to use Realm and Retrofit, First I want to get the data from retrofit and then get the data from my remote webservice then, use realm's insertOrUpdate to merge the remote objects with the local one. I'm able to get on this process so far but when I looked into my Network requests on stetho, this method is complete requesting infinite times. Where did I go wrong? Here's the function
public Observable<RealmResults<Event>> all() {
Realm realm = Realm.getDefaultInstance();
return realm.where(Event.class).findAllAsync()
.asObservable()
.filter(new Func1<RealmResults<Event>, Boolean>() {
#Override
public Boolean call(RealmResults<Event> events) {
return events.isLoaded();
}
})
.doOnNext(new Action1<RealmResults<Event>>() {
#Override
public void call(RealmResults<Event> events) {
service.getEvents()
.subscribeOn(Schedulers.io())
.subscribe(new Action1<List<Event>>() {
#Override
public void call(final List<Event> events) {
try(Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.insertOrUpdate(events);
}
});
} // auto-close
}
});
}
});
}
and here's the function on my activity, where I use it
private void getEvents() {
Log.i("EVENTSELECTION", "STARTING");
repository.all()
.subscribe(new Subscriber<List<Event>>() {
#Override
public void onCompleted() {
Log.i("EVENTSELECTION", "Task Completed");
swipeRefreshLayout.setRefreshing(false);
}
#Override
public void onError(Throwable e) {
Log.e("EVENTSELECTION", e.getMessage());
swipeRefreshLayout.setRefreshing(false);
e.printStackTrace();
}
#Override
public void onNext(List<Event> events) {
Log.i("EVENTSELECTION", String.valueOf(events.size()));
}
});
}
Thank you so much.

Where did I go wrong?
Let's go through it:
1.
public Observable<RealmResults<Event>> all() {
Realm realm = Realm.getDefaultInstance();
This opens a Realm instance that will never be closed. So your Realm lifecycle management is wrong, refer to the documentation for best practices.
2.
return realm.where(Event.class).findAllAsync()
.asObservable() // <-- listens for changes in the Realm
// ...
.doOnNext(new Action1<RealmResults<Event>>() {
#Override
public void call(RealmResults<Event> events) {
service.getEvents() // <-- downloads data
.subscribeOn(Schedulers.io())
.subscribe(new Action1<List<Event>>() {
You basically say that "in case there are any changes made to data in Realm, then download data from the service and write it into the Realm"
Which will trigger the RealmChangeListener which will trigger a download and so on.
This is a conceptual error, you're using Realm notifications incorrectly.
RealmResults<T> is not just a list of objects, it is also a subscription for changes. So you need to keep it as a field reference, and "stay subscribed to changes in the database".
RealmResults<Sth> results;
RealmChangeListener<RealmResults<Sth>> changeListener = (element) -> {
if(element.isLoaded()) {
adapter.updateData(element);
}
};
void sth() {
results = realm.where(Sth.class).findAllSortedAsync("id");
results.addChangeListener(changeListener);
}
void unsth() {
if(results != null && results.isValid()) {
results.removeChangeListener(changeListener);
results = null;
}
}
In your case, RealmResults<T> which symbolizes a subscription and also provides access to the current/new data is wrapped as an Observable<T> which you can create subscribers to.
Observable<List<<Sth>> results;
Subscription subscription;
Action1<List<Sth>> changeListener = (element) -> {
if(element.isLoaded()) {
adapter.updateData(element);
}
};
void sth() {
results = realm.where(Sth.class).findAllSortedAsync("id").asObservable();
subscription = results.subscribe(changeListener);
}
void unsth() {
if(subscription != null && !subscription.isUnsubscribed()) {
subscription.unsubscribe();
subscription = null;
results = null;
}
}
As you can see, you have a subscription at the start of the component, and an unsubscription at the end of the component.
Calling Observable.first() is incorrect, it does not make sense to do that. If you saw it in any tutorial (I've seen it before...), then that tutorial was wrong.

So it's really a by design on realm and it won't call the onCompleted, I added a .first() at the end of my getEvents function to get only the first result.

Related

How to convert a pre-instanced callback into Observable

I'm fairly new to RxJava and I have a basic understanding as to how to wrap a callback into an Observable but what I'm having difficulty with is doing so when the callback/listener is pre-instanced. Every example that I have found only shows instancing the callback directly into the Observable being created.
Some example code of what I'm talking about. I'm working with an Api that's works like this:
public class Api {
private ApiCallback callback;
void initialize(ApiCallback callback){
this.callback = callback;
}
void doAction1(){
this.callback.onAction1Complete();
}
}
interface ApiCallback {
void onInitialized();
void onAction1Complete();
}
With the real api I am working with I have no control over how it works so I must work with it in this state. In terms of trying to work with this Api using observables here is the struggle I am having. I have a member variable that holds the Api object:
private Api mApi = new Api();
Now in order to initialize this I have one of two options it seems.
Option 1:
Completable startApi() {
return Completable.create(new CompletableOnSubscribe() {
#Override
public void subscribe(final CompletableEmitter emitter) throws Exception {
mApi.initialize(new ApiCallback() {
#Override
public void onInitialized() {
emitter.onComplete();
}
#Override
public void onAction1Complete() {
}
});
}
});
}
Option 2:
private ApiCallback premadeCallback = new ApiCallback() {
#Override
public void onInitialized() {
}
#Override
public void onAction1Complete() {
}
};
Completable startApi() {
return Completable.create(new CompletableOnSubscribe() {
#Override
public void subscribe(final CompletableEmitter emitter) throws Exception {
mApi.initialize(premadeCallback);
}
});
}
Now the issue I have is that Option 2 makes more sense to me when I need to know when the other methods in the callback are called from Api calls. With my understanding of RxJava however I don't understand how I can reach these method calls with an Api that works like this.
For example:
Completable doAction1() {
return Completable.create(new CompletableOnSubscribe() {
#Override
public void subscribe(final CompletableEmitter emitter) throws Exception {
// Api is already initialized with callback
// How do I reach the callback from here?
}
});
}
The only what that I can currently think of as to how to achieve this would be to create a member variable as an emitter (or a dictionary of emitters) and then call its appropriate method in the api callback when needed. My concerns with this are A. I'm unsure if RxJava can work this way B. This sounds like a terrible idea.

Multiple async calls best practices in Android

I need a shorter & cleaner solution for Example 1. So multiple async calls need to be finished before a certain Activity/Fragment can start. Example 1 is very messy and ugly with member bools, but works.
I was considering using the Google Tasks API. But for that I need to add a google-services.json and connect to either "Google Sign-in", "Analytics" or "Cloud messaging", which I don't need I think. There must be a better way or is this the correct way to go?
Example 1:
boolean mIsFirstDone = false;
boolean mIsSecondDone = false;
boolean mAlreadyDone = false;
private void prepareSomeData() {
dataManager.requestSomeContent(new ApiCallback() {
#Override
public void onSuccess(final Object object) {
mIsFirstDone = true;
if(mIsFirstDone && mIsSecondDone && !mAlreadyDone) {
mAlreadyDone = true;
doSomething();
}
}
});
}
private void prepareSomeSettings() {
dataManager.requestSomeSettings(new ApiCallback() {
#Override
public void onSuccess(final Object object) {
mIsSecondDone = true;
if(mIsFirstDone && mIsSecondDone && !mAlreadyDone) {
mAlreadyDone = true;
doSomething();
}
}
});
}
With Tasks API:
Tasks.whenAll(SomeDataTask, SomeSettingsTask).addOnSuccessListener(executor, new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void v) {
doSomething();
}
}).addOnFailureListener(executor, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
}
});
RxJava, as pointed out, is probably a better solution to this. The reason why is because you can chain multiple api requests, database requests into a concrete block of code that looks elegant and clean. As an example, see below of what I'm trying to say:
Subscription subscription = apiService.getUser(someId)
.flatMap(user -> apiService.getFavourites(user.getFavouritesTag())
.subscribe(favourites -> view.updateFavouritesList(favourites),
throwable -> Log.e(TAG, throwable.printStackTrace());
Have you considered learning about RxJava and reformatting all your projects to RxJava along with retrofit for API?
start with something like this:
https://medium.com/yammer-engineering/chaining-multiple-sources-with-rxjava-20eb6850e5d9
https://adityaladwa.wordpress.com/2016/05/11/dagger-2-and-mvp-architecture/

Update UI on insertions to Database using RxJAVA and DAO

I have a ChatThreadsActivity() which displays messages between two people.
Whenever the user opens the activity, I make an API to fetch the most recent 100 messages. So in the onCreate() method I call makeApiRequestToGetChatThread().
I know I am doing alot of things wrong here.
Inside onNext() I add rows to the chatThreadAdapter. I know this is wrong since I update adapter after ever insert.
chatDaoObject.queryChatThreadsFromDB(someId).observeOn(AndroidSchedulers.mainThread()).subscribe()
Should this be anonymously called? When will this be unsubscribed if ever?
How do I unsubscribe from it?
I've read about BackPressure and realize this is it, I've run this on Android Monitor and here's how I can identify it. Am I doing this right?
private void makeApiRequestToGetChatThreads() {
public void onResponse(Call call, final Response response) {
final String responseString = response.body().string();
runOnUiThread (() -> {
final JSONArray array = new JSONArray(responseString);
JSONObject obj;
for (int i=0 ; i < array.length ; i++) {
obj = new JSONObject(array.get(i));
insertAChatIntoDB (obj);
}
}
});
}
private void insertAChatIntoDB(JSONObject o) {
if(insertSubscriber != null) {
insertSubscriber.unsubscribe();
}
insertSubscriber = new Subscriber<Long>() {
public void onCompleted() {
}
public void onError() {
}
public void onNext() {
chatDaoObject.queryChatThreadsFromDB(someId)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<List<ChatObject>>() {
#Override
public void call(List<ChatObject> chatList) {
if (chatList != null) {
//Royal Screw up
//After every insertion in the DB I update the UI
chatThreadAdapter.addAllChatThreadsIntoList(chatList);
//Notify is called inside the above below
//notifyItemRangeChanged(initialPosition,chatList.size())
}
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
}
});
}
};
try {
//Inserts happening on Schedulers.computation()
chatDaoObject.insertAChatInDB(o).observeOn(Schedulers.computation()).subscribe(insertSub);
} catch (JSONException e) {
e.printStackTrace();
Crashlytics.logException(e);
}
}
I use RxJAVA and SQLBrite Dao and here's what the query looks like:
//In `ChatDao` class
ChatDataDao extends Dao {
...
...
public Observable<long> insertAChatInDB(JSONObject o) {
ChatThreadsTable.ContentValuesBuilder valuesBuilder = ChatThreadsTable.contentValues();
...
//populating columns values
...
return insert(ChatThreadsTable.TABLE_NAME, valuesBuilder.build(), SQLiteDatabase.CONFLICT_IGNORE);
}
public Observable<List> queryChatThreadsFromDB () {
return rawQuery("SELECT * FROM " + ChatThreadsTable.TABLE_NAME).run().mapToList(ChatObjectMapper.MAPPER);
}
...
...
}
Edit:
Is this the right way to query db without worrying about subscription/unsubscription?
rawQuery("SELECT * FROM " + SomeObject.TABLE_NAME + " ORDER BY " + SomeObject.COL1 + " , " +SomeObject.COL2 + " DESC").run().mapToList(SomeObjectMapper.MAPPER)
.flatMap(new Func1<List<SomeObject>, Observable<SomeObject>>() {
#Override
public Observable<SomeObject> call(List<SomeObject> SomeObjects) {
return Observable.from(SomeObjects);
}
}).doOnNext(new Action1<SomeObject>() {
#Override
public void call(SomeObject chatThreadObject) {
}
}).subscribe();
what is going on with all these tutorials telling people to create their own Subscribers? Here's a cleaned up version:
private void makeApiRequestToGetChatThreads() {
// ... some call that calls back onResponse
}
public void onResponse(Call call, final Response response) {
// are you sure this is how you parse a JSON String?
Observable
.from(response.body().string())
.observeOn(Schedulers.computation())
.flatMapIterable(JsonArray::new)
.map(JSONObject::new)
.flatMap(chatDaoObject::insertAChatInDB)
.flatMap(chatDaoObject::queryChatThreadsFromDB)
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(chatThreadAdapter::addAllChatThreadsIntoList)
.subscribe(dummy -> {}, throwable -> {
e.printStackTrace();
Crashlytics.logException(e);
});
}
It's quite likely that there's a better way to directly make the network call return an Observable. Check your documentation / peers.
When the Observable completes all processing and UI updates, it will unsubscribe on it's own. However, if the network call takes a while to complete, the user might have already switched screens/apps, and the UI manipulation will break your app. Consider saving the subscription in your view and unsubscribing. Hint: creating the subscription in the onResponse is... not optimal.
You are getting 100s of threads, and updating the UI for each and every one of them. Are you sure you want that?
I don't think you need to care about backpressure.

Avoid using CountDownLatch to wait for many threads before sending the callback

So I have a list of Track Ids that for each track Id I need to execute a network request to get the track details, I am using a for loop to launch all the requests and a latch to wait for all the requests to be completed. After they are completed then the callback is sent with the List of Tracks that have already populated.
I would like to know if there is any better way to do this, maybe with RxJava ?
I am using Retrofit 2.0 in Android.
public IBaseRequest batchTracksById(final TrackIdList trackIdListPayload, final IRequestListener<TracksList> listener) {
final TracksList tracks = new TracksList();
final Track[] trackArray = newrack[trackIdListPayload.getTrackIds().length];
tracks.setTrack(trackArray);
final CountDownLatch latch = new CountDownLatch(trackArray.length);
Thread t = new Thread(new Runnable() {
#Override
public void run() {
try {
latch.await();
handler.post(new Runnable() {
#Override
public void run() {
listener.onRequestUpdate(null, tracks, null, true);
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
for (String id : trackIdListPayload.getTrackIds()) {
getTrackById(id, new IRequestListener<Track>() {
#Override
public void onRequestFailure(IBaseRequest request, Exception exception) {
latch.countDown();
}
#Override
public void onRequestUpdate(IBaseRequest request, Track track, RequestState state, boolean requestComplete) {
//iterate through the tracks and update the ones in the thing
int i = 0;
for (String s : trackIdListPayload.getTrackIds()) {
if (s.equals(track.getTrackId())) {
trackArray[i] = track;
// don't break here, as we may have a case where we have multiple instances of the same trackId (although
// at the moment a request will be made for each anyway...
}
i++;
}
latch.countDown();
}
});
}
return null;
}
If you want to make all the requests asynchronously and wait for them to return you can do this (lambdas for brevity and readability):
tracks.flatMap(track -> getTrackDetails(track.id)
.subscribeOn(Schedulers.io()))
.toList()
.doOnNext(list -> processTrackList())
...
If you require that the results are returned in the order of tracks but are still requested asynchronously then in soon to be released rxjava 1.0.15 you will be able to do this
tracks.concatMapEager(track -> getTrackDetails(track.id)
.subscribeOn(Schedulers.io())
.toList()
.doOnNext(list -> processTrackList())
...
If I understand correctly, you have a list of tracks as input and you want a list of webservice results. Here's a simple way to do that with RxJava if you can make your network call synchronous (rxjava will handle the background processing for you).
Observable.from(trackList)
.map(new Func1<Track, Response>() {
#Override
public Response call(Track track) {
return makeRequestSynchronously(track.id());
}
})
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<Response>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<Response> responses) {
}
});
Edit: You can change Retrofit to return observables from webservice, if you do that you will need to change map to the following
.flatMap(new Func1<Track, Observable<Response>>() {
#Override
public Observable<Response> call(Track track) {
return makeRequestThatReturnsObservable(track.id());
}
})

How can I reuse a Subscriber between two Observables (RxJava)

In order to not repeat myself, I want to re-use a Subscriber variable between two observables. How do you do accomplish this? My current code below does not work, because after the subscriber is used once, it is unsubscribed and no longer works again. If I new a Subscriber instead of reusing a variable, my subscription works. I don't want to write the same code twice, if possible.
public class HomePresenter extends BasePresenter<HomeView> {
ArticleRepo articleRepo;
#Inject
public HomePresenter(ArticleRepo articleRepo) {
this.articleRepo = articleRepo;
}
#Override
public void onCreate(#Nullable PresenterBundle bundle) {
super.onCreate(bundle);
}
public void onEvent(ArticleCategoryClickedEvent event) {
Timber.v("Adapter position clicked at position: '%d'", event.getAdapterPosition());
view.launchArticleActivity(event.getArticleCategory());
}
public void onEvent(SeabeeOnlineExternalLinkClickedEvent event) {
view.launchExternalLink(event.getSeabeeOnlineExternalLink());
}
public void loadArticleImages() {
articleRepo.getArticleBuckets()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
public void refreshData() {
articleRepo.refreshAndSaveArticles()
.flatMap(new Func1<List<ArticleEntity>, Observable<List<ImageArticleCategoryEntity>>>() {
#Override
public Observable<List<ImageArticleCategoryEntity>> call(List<ArticleEntity> articleEntityList) {
return articleRepo.getArticleBuckets();
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
final Subscriber<List<ImageArticleCategoryEntity>> subscriber = new Subscriber<List<ImageArticleCategoryEntity>>() {
#Override
public void onCompleted() {
Timber.v("Loading article images complete!");
view.hideLoadingAnimation();
}
#Override
public void onError(Throwable e) {
Timber.e("Error loading article images", e);
Log.e("tag", "Error loading article images", e);
}
#Override
public void onNext(List<ImageArticleCategoryEntity> integerImageArticleCategoryEntityHashMap) {
view.loadArticleImages(integerImageArticleCategoryEntityHashMap);
}
};
}
A Subscriber should not be reused. It will not work because it is a Subscription and once unsubscribed it is done.
Use an Observer instead if you want to reuse it.
source
You can reuse your subscriber, you just need to create an actual class out of it.
private static class MySubscriber extends Subscriber<List<ImageArticleCategoryEntity>> {...}
Subscriber<> subscriber1 = new MySubscriber();
Subscriber<> subscriber2 = new MySubscriber();
And there you go.

Categories