RxJava Realm findFirstAsync() result is not loaded, findFirst() fires endlessly - android

Why does this code endlessly execute it's .subscribe(..) part? I assumed that event will be fired only once, when matching Content object will be found. But it starts over and over again.
realm.where(Content.class)
.equalTo("keyID", id)
.findFirst()
.<Content>asObservable()
.map(this::getPostFromContent)
.subscribe(post -> {
loadComments(post.getId());
});
And if I change to .findFirstAsync() it throws an exception:
"Can't access a row that hasn't been loaded, make sure the instance is loaded by calling RealmObject.isLoaded()"
I am using latest version of realm.
UPDATE
I was able to make this work by using:
realm.where(RealmWrappedContent.class)
.equalTo("keyID", id)
.findFirstAsync()
.<RealmWrappedContent>asObservable()
.filter(post -> post.isLoaded())
.first()
.map(this::getPostFromContent)
.subscribe(post -> {
loadComments(post.getId());
});
But that's way too many thing you need to write just to use realm objects as observables. Is there any better way?

The default behaviour is submitting each time there is an update. In that case you need to use filter/first to only get 1 item as you found out.
If you want to change that behaviour in your entire app, you can also provide your own RxObservableFactory implementation as described here: https://realm.io/docs/java/latest/#rxjava.
The default factory is called RealmObservableFactory and it should be fairly easy to either wrap that or provide your own implementation that does what your want for all observables.

Related

Can I modify the data set of a PagingDataAdapter using peek()?

I am looking for a way to update specific items in my PagingDataAdapter from the Paging 3 library. The recommended way at the moment seems to be to invalidate the PagingSource but this causes the adapter to fetch the whole data set again, which is not efficient and also shows my loading spinner.
However, I noticed that I can access and modify items in the adapter using the peek() method and it seems to work quite well. Am I missing anything here? Will this fall apart in certain scenarios? I know that it's good practice to keep data classes immutable but this approach makes my life a lot easier.
Here is an example of my usage and it seems to work quite well:
viewModel.chatMessageUpdateEvents.collect { messageEvent ->
when (messageEvent) {
is FirestoreChatMessageListener.ChatMessageUpdateEvent.MessageUpdate -> {
val update = messageEvent.chatMessage
val historyAdapterItems = chatMessagesHistoryAdapter.snapshot().items
val updatedMessage =
historyAdapterItems.find { chatMessage ->
chatMessage.documentId == messageEvent.chatMessage.documentId
}
if (updatedMessage != null) {
val messagePosition = historyAdapterItems.indexOf(updatedMessage)
chatMessagesHistoryAdapter.peek(messagePosition)?.unsent = update.unsent
chatMessagesHistoryAdapter.peek(messagePosition)?.imageUrl = update.imageUrl
chatMessagesHistoryAdapter.notifyItemChanged(messagePosition)
}
}
}
}
I replied in a separate comment but wanted to post here for visibility.
This is really not recommended and a completely unsupported usage of paging.
One of the primary ways of restoring state if Pager().flow() hasn't been cleaned up (say if ViewModel hasn't been cleared yet) is via the .cachedIn(scope) method, which will cache out-of-date data in your case. This is also the only way to multicast (make the loaded data in PagingData re-usable) for usage in Flow operations like .combine() that allow you to mix transformations with external signals.
You'll also need to handle races between in-flight loads, what happens if you get a messageEvent the same time an append finishes? Who wins in this case and is it possible between taking the .snapshot() a new page is inserted so your notify position is no longer correct?
In general it's much simpler to have a single source of truth and this is the recommended path, so the advice has always been to invalidate on every backing dataset update.
There is an open FR in Paging's issue tracker to support Flow<Item> or Flow<Page> style data to allow granular updates, but it's certainly a farther future thing: https://issuetracker.google.com/160232968

Why addSnapshotListener is called twice on firestore CollectionReference? [duplicate]

My firestore onSnapshot() function is being called twice.
let user = firebase.firestore().collection('users').doc(userID).onSnapshot
({
next: (documentSnapshot: firebase.firestore.DocumentSnapshot) =>
{
this.userArray.push(documentSnapshot as User);
console.log(documentSnapshot);
//here
},
error: (firestoreError: firebase.firestore.FirestoreError) =>
{
console.log(firestoreError);
//here
}
});
I have also tried subscribing like in https://firebase.google.com/docs/firestore/query-data/listen#detach_a_listener by including user() at the //here comment but to no avail.
How can I modify such that the function only executes one time, i.e. push only one user object per time instead of twice.
I don't know if this is related to your question. If one is using
firebase.firestore.FieldValue.serverTimestamp()
to give a document a timestamp, then onSnaphot will fire twice. This seem to be because when you add a new document to your database onSnapshot will fire, but the serverTimestamp has not run yet. After a few milliseconds serverTimestamp will run and update you document => onSnapshot will fire again.
I would like to add a small delay before onSnapshot fires (say 0,5s or so), but I couldn't find the way to do this.
You can also make a server side function for onCreate event, I believe that would solve your problem. Maybe your userArray.push-action would be more suitable to execute in server side.
Update: To learn more about the behavior of serverTimestamp() and why it triggers the listener twice read this article: The secrets of Firestore’s FieldValue.serverTimestamp() — REVEALED!. Also, the official documentation states:
When you perform a write, your listeners will be notified with the new data before the data is sent to the backend.
In the article there are a couple of suggested solutions, one of which is to use the metadata property of the snapshot to find whether the Boolean value of metadata.hasPendingWrites is true (which tells you that the snapshot you’re looking at hasn’t been written to the server yet) or false.
For example, in your case you can check whether hasPendingWrites is false and then push the object:
if ( !documentSnapshot.metadata.hasPendingWrites ){
// This code will only execute once the data has been written to the server
this.userArray.push(documentSnapshot as User);
console.log(documentSnapshot);
}
In a more generic example, the code will look like this:
firestore.collection("MyCollection")
.onSnapshot( snapshot => {
if ( snapshot.metadata.hasPendingWrites ){
// Local changes have not yet been written to the backend
} else {
// Changes have been written to the backend
}
});
Another useful approach, found in the documentation is the following:
If you just want to know when your write has completed, you can listen to the completion callback rather than using hasPendingWrites. In JavaScript, use the Promise returned from your write operation by attaching a .then() callback.
I hope these resources and the various approaches will help anyone trying to figure out a solution.
REFERENCES:
Events for local changes
The hasPendingWrites metadata property
Snapshot Listen Options
If you need a one time response, use the .get() method for a promise.
firebase.firestore().collection('users').doc(userID).get().then(snap => {
this.userArray = [...this.userArray, snap.doc);
});
However, I suggest using AngularFire (totally biased since I maintain the library). It makes handling common Angular + Firebase tasks much easier.

RxJava 2 Nested Network Requests

in the app I am currently working on I use retrofit to create an Observable <ArrayList<Party>>.
Party has a hostId field as well as a field of type User which is null at the point of creation by Retrofits GsonConverter. I now want to use hostId to make a second request getting the user from id and adding the User to the initial Party. I have been looking into flatmap but I haven't found an example in which the first observable's results are not only kept but also modified.
Currently, to get all parties without the User I am doing :
Observable<ArrayList<Party>> partiesObs = model.getParties();
partiesObs.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::handlePartyResponse, this::handleError);
How would I go about adding User to every Party without having to call model.getUsers() in the onSuccess() method of the inital call and then having to iterate through the two lists?
I understand that flatmap() returns a new Observable while map doesn't but I am unsure about how to use either in this scenario.
Thank you
As in the comment, you should try and get the backend API changed for something like this to avoid an inelegant and inefficient solution.
If this is not feasible, you could probably do something like this:
.flatMapIterable(list -> list)
.flatMap(party -> model.getUser(party.hostId),
(party, user) -> new Party(user, party.hostId, party.dontCare))
Where:
flatMapIterable flattens the Observable<ArrayList<Party>> into an Observable<Party>
The overload of flatMap takes a Function for transforming emissions (Party objects) into an ObservableSource (of User objects) as the first parameter. The second parameter is a BiFunction for combining the Party and User objects which you can use to create a fully fledged Party object.
The last step is much easier if you have a copy or clone operation on the Party object that takes a previous instance and adds fields to it.

RxJava: How to wait for all subscriptions to complete?

I am fairly new to rxJava, trying stuff by my own. I would like to get some advice if I'm doing it right.
Usecase: On the first run of my app, after a successful login I have to download and save in a local database several dictionaries for the app to run with. The user has to wait till the downloading process finishes.
Current solution: I am using retrofit 2 with rxjava adapter in order to get the data. I am bundling all Observables into one using the zip operator. After all downloads are done the callback triggers and saving into database begins.
Nothing speaks better than some code:
Observable<List<OrderType>> orderTypesObservable = backendService.getOrderTypes();
Observable<List<OrderStatus>> orderStatusObservable = mockBackendService.getOrderStatuses();
Observable<List<Priority>> prioritiesObservable = backendService.getPriorities();
return Observable.zip(orderTypesObservable,
orderStatusObservable,
prioritiesObservable,
(orderTypes, orderStatuses, priorities) -> {
orderTypeDao.deleteAll();
orderTypeDao.insertInTx(orderTypes);
orderStatusDao.deleteAll();
orderStatusDao.insertInTx(orderStatuses);
priorityDao.deleteAll();
priorityDao.insertInTx(priorities);
return null;
});
Questions:
Should I use the zip operator or is there a better one to fit my cause?
It seems a bit messy doing it this way. This is only a part of the code, I have currently 12 dictionaries to load. Is there a way to refactor it?
I would like to insert a single dictionary data as soon as it finishes downloading and have a retry mechanism it the download fails. How can I achieve that?
I think in your case it's better to use Completable, because for you matter only tasks completion.
Completable getAndStoreOrderTypes = backendService.getOrderTypes()
.doOnNext(types -> *store to db*)
.toCompletable();
Completable getAndStoreOrderStatuses = backendService.getOrderStatuses()
.doOnNext(statuses -> *store to db*)
.toCompletable();
Completable getAndStoreOrderPriorities = backendService.getOrderPriorities()
.doOnNext(priorities -> *store to db*)
.toCompletable();
return Completable.merge(getAndStoreOrderTypes,
getAndStoreOrderStatuses,
getAndStoreOrderPriorities);
If you need serial execution - use Completable.concat() instead of merge()
a retry mechanism if the download fails
Use handy retry() operator
It is not good, to throw null value object into Rx Stream (in zip your return null, it is bad).
Try to not doing that.
In your case, you have 1 api call and 2 actions to save response into the database, so you can create the chain with flatMap.
It will look like:
backendService.getOrderTypes()
.doOnNext(savingToDatabaseLogic)
.flatMap(data -> mockBackendService.getOrderStatuses())
.doOnNext(...)
.flatMap(data -> backendService.getPriorities())
.doOnNext(...)
if you want to react on error situation, in particular, observable, you can add onErrorResumeNext(exception->Observable.empty()) and chain will continue even if something happened
Also, you can create something like BaseDao, which can save any Dao objects.

RxJava and Cached Data

I'm still fairly new to RxJava and I'm using it in an Android application. I've read a metric ton on the subject but still feel like I'm missing something.
I have the following scenario:
I have data stored in the system which is accessed via various service connections (AIDL) and I need to retrieve data from this system (1-n number of async calls can happen). Rx has helped me a ton in simplifying this code. However, this entire process tends to take a few seconds (upwards of 5 seconds+) therefore I need to cache this data to speed up the native app.
The requirements at this point are:
Initial subscription, the cache will be empty, therefore we have to wait the required time to load. No big deal. After that the data should be cached.
Subsequent loads should pull the data from cache, but then the data should be reloaded and the disk cache should be behind the scenes.
The Problem: I have two Observables - A and B. A contains the nested Observables that pull data from the local services (tons going on here). B is much simpler. B simply contains the code to pull the data from disk cache.
Need to solve:
a) Return a cached item (if cached) and continue to re-load the disk cache.
b) Cache is empty, load the data from system, cache it and return it. Subsequent calls go back to "a".
I've had a few folks recommend a few operations such as flatmap, merge and even subjects but for some reason I'm having trouble connecting the dots.
How can I do this?
Here are a couple options on how to do this. I'll try to explain them as best I can as I go along. This is napkin-code, and I'm using Java8-style lambda syntax because I'm lazy and it's prettier. :)
A subject, like AsyncSubject, would be perfect if you could keep these as instance states in memory, although it sounds like you need to store these to disk. However, I think this approach is worth mentioning just in case you are able to. Also, it's just a nifty technique to know.
AsyncSubject is an Observable that only emits the LAST value published to it (A Subject is both an Observer and an Observable), and will only start emitting after onCompleted has been called. Thus, anything that subscribes after that complete will receive the next value.
In this case, you could have (in an application class or other singleton instance at the app level):
public class MyApplication extends Application {
private final AsyncSubject<Foo> foo = AsyncSubject.create();
/** Asynchronously gets foo and stores it in the subject. */
public void fetchFooAsync() {
// Gets the observable that does all the heavy lifting.
// It should emit one item and then complete.
FooHelper.getTheFooObservable().subscribe(foo);
}
/** Provides the foo for any consumers who need a foo. */
public Observable<Foo> getFoo() {
return foo;
}
}
Deferring the Observable. Observable.defer lets you wait to create an Observable until it is subscribed to. You can use this to allow the disk cache fetch to run in the background, and then return the cached version or, if not in cache, make the real deal.
This version assumes that your getter code, both cache fetch and non- catch creation, are blocking calls, not observables, and the defer does work in the background. For example:
public Observable<Foo> getFoo() {
Observable.defer(() -> {
if (FooHelper.isFooCached()) {
return Observable.just(FooHelper.getFooFromCacheBlocking());
}
return Observable.just(FooHelper.createNewFooBlocking());
}).subscribeOn(Schedulers.io());
}
Use concatWith and take. Here we assume our method to get the Foo from the disk cache either emits a single item and completes or else just completes without emitting, if empty.
public Observable<Foo> getFoo() {
return FooHelper.getCachedFooObservable()
.concatWith(FooHelper.getRealFooObservable())
.take(1);
}
That method should only attempt to fetch the real deal if the cached observable finished empty.
Use amb or ambWith. This is probably one the craziest solutions, but fun to point out. amb basically takes a couple (or more with the overloads) observables and waits until one of them emits an item, then it completely discards the other observable and just takes the one that won the race. The only way this would be useful is if it's possible for the computation step of creating a new Foo to be faster than fetching it from disk. In that case, you could do something like this:
public Observable<Foo> getFoo() {
return Observable.amb(
FooHelper.getCachedFooObservable(),
FooHelper.getRealFooObservable());
}
I kinda prefer Option 3. As far as actually caching it, you could have something like this at one of the entry points (preferably before we're gonna need the Foo, since as you said this is a long-running operation) Later consumers should get the cached version as long as it has finished writing. Using an AsyncSubject here may help as well, to make sure we don't trigger the work multiple times while waiting for it to be written. The consumers would only get the completed result, but again, that only works if it can be reasonably kept around in memory.
if (!FooHelper.isFooCached()) {
getFoo()
.subscribeOn(Schedulers.io())
.subscribe((foo) -> FooHelper.cacheTheFoo(foo));
}
Note that, you should either keep around a single thread scheduler meant for disk writing (and reading) and use .observeOn(foo) after .subscribeOn(...), or otherwise synchronize access to the disk cache to prevent concurrency issues.
I’ve recently published a library on Github for Android and Java, called RxCache, which meets your needs about caching data using observables.
RxCache implements two caching layers -memory and disk, and it counts with several annotations in order to configure the behaviour of every provider.
It is highly recommended to use with Retrofit for data retrieved from http calls. Using lambda expression, you can formulate expression as follows:
rxCache.getUser(retrofit.getUser(id), () -> true).flatmap(user -> user);
I hope you will find it interesting :)
Take a look at the project below. This is my personal take on things and I have used this pattern in a number of apps.
https://github.com/zsiegel/rxandroid-architecture-sample
Take a look at the PersistenceService. Rather than hitting the database (or MockService in the example project) you could simply have a local list of users that are updated with the save() method and just return that in the get().
Let me know if you have any questions.

Categories

Resources