I use Realm.getDefaultInstance() in a worker thread (in a service) and save my records with:
Realm realm = Realm.getDefaultInstance();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
...
realm.copyToRealmOrUpdate(...
When done (this is sync, so it is done right away), I submit an EventBus message to the UI thread that it can start the next task.
The UI thread calls to a different worker thread again which does another Realm.getDefaultInstance() and then a findAll() call. It finds nothing. Why is this? "Some time later" it will find the items fine, but not right away.
The items are in the database already: If I do the findAll() right after saving the items on the first worker thread, it does return the items. If I do it AFTER that again on the second worker thread it finds nothing. Is this some kind of caching?
The problem was based on me using a threadpool, on which threads the Realm instance was not updated. A solution for me is to not use the threadpool, so instead of
observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
I now use this everywhere:
observable
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
And it solves all Realm threading problems. It is a little less efficient probably, but fine for my purpose.
Related
I'm trying to implement a simple chat application on web sockets in Clean Architecture. I had to choose a db for caching all information, so I decided to use Realm, because I heard it was pretty good database for any kind of mobile applications. But when I actually faced the Realm, it turned out to be really painful experience for me to implement caching logic with it.
All problems come from applying transaction to database which then must be synced on all threads working with Realm. There seems to some kind of synchronization problem with my code. For example, I want to save my object to Realm and then query it out of.
Here I have two simple functions to save and to get chat:
fun getBackgroundLooper(): Looper {
val handlerThread = HandlerThread("backgroundThread")
if (!handlerThread.isAlive)
handlerThread.start()
return handlerThread.looper
}
fun saveChat(chat: Chat): Completable {
val realmChat = ChatMapper.domainToCache(chat)
return Completable.create { e ->
val realm = Realm.getDefaultInstance()
realm.executeTransactionAsync({
it.insertOrUpdate(realmChat)
}, {
realm.close()
e.onComplete()
}, {
realm.close()
e.onError(it)
})
// Subscribe on background looper thread
// to be able to execute async transaction
}.subscribeOn(AndroidSchedulers.from(getBackgroundLooper()))
}
fun getSingleChat(chatId: String): Single<Chat> {
return Single.defer {
val realm = Realm.getDefaultInstance()
realm.isAutoRefresh = true
val realmChat = realm.where(RealmChat::class.java)
.equalTo("id", chatId).findFirstAsync()
if (realmChat.isValid) {
realmChat.load()
val chat = ChatMapper.cacheToDomain(realmChat)
realm.close()
Single.just(chat)
}
realm.close()
Single.error<Chat>(ChatNotExistException())
// Subscribe on background looper thread
// to be able to execute auto refreshing
}.subscribeOn(AndroidSchedulers.from(getBackgroundLooper()))
}
So, when I try to run simple code like this
remote.getChat().flatMap {
cache.saveChat(it) //save chat to realm
.andThen(cache.getSingleChat(it.id)) //then query it by id
}
I always get no matter of what ChatNotExistException, but if I try to run query again in another attempt or after restarting the application, then the chat object gets found
I also tried many different approaches to execute this code:
I tried to use realm.refresh() in getSingleChat or not use it at all.
I tried to query chat synchronously with findFirst() and findAll() instead of findFirstAsync().
I tried querying chat on current thread without .subscribeOn().
I tried to use realm.executeTransaction() instead of async transactions.
I tried to add thread sleep between saving and querying, so that transaction may take some time to get applied and I need to wait before attempting to query the chat
I'm begging anybody to explain me what am I doing wrong and how to make this code working. I can't change the architecture of my application and use Realm objects as my view models, I need to find solution in these conditions.
But when I actually faced the Realm, it turned out to be really painful experience for me to implement caching logic with it.
Reading the docs regarding best practices help. For example, the default idea is that you define a RealmResults using an async query on the UI thread, add a change listener to it, and observe the latest emission of the database.
There is no "caching" involved in that beyond saving to the database and observing the database. Any additional complexity is added by you and is completely optional.
All problems come from applying transaction to database which then must be synced on all threads working with Realm.
All looper threads automatically make the Realm auto-refresh, therefore if addChangeListener is used as intended in the docs, then there is no need for trickery, Realm will manage the synchronization between threads.
I want to save my object to Realm and then query it out of.
realm.executeTransactionAsync({
No reason to use executeTransactionAsync when you are already on a background thread.
try(Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction((r) -> {
// do write here
});
}
realm.where(RealmChat::class.java)
If you do import io.realm.kotlin.where, then you can do realm.where<RealmChat>().
.findFirstAsync()
No reason to use findFirstAsync() instead of findFirst() when you are already on a background thread. Also no reason to use load() when you're on a background thread, because you should be using findFirst() in the first place anyway.
You are also most likely missing a return#defer Single.just(chat) to actually return the chat if it's found. That is most likely what your original problem is.
With the handler thread things you're doing though, you might want to consider taking a look at this project called "Monarchy", as it intends to set up the ability to run queries on a background looper thread while still observing the results. It is labelled stagnant but the ideas are sound.
I'm seeing a very weird problem with getting the count from a query not running on the main thread. I have a UI that uses the RecyclerView adapter for Realm, and it works just dandy.
I have a method that counts the number of records before a query so it can set a starting point, for some reason it will return the previous count was before the last transaction. Here is a somewhat shortened version with the output of the log:
D/SearchController: Query Count (Main Thread): 50
D/SearchController: Query Count (Rx Thread): 50
D/SearchController: Query Count (Main Thread): 100
D/SearchController: Query Count (Rx Thread): 50
public Single<Update> searchWithCriteriaForMore(SearchCriteria searchCriteria) {
Realm realmI = Realm.getDefaultInstance();
Timber.d("Query Count (Main Thread): %d", realmI.where(Ad.class).equalTo("searchId", searchCriteria.id()).count());
realmI.close();
return Single.defer(() -> {
Realm realm = Realm.getDefaultInstance();
Timber.d("Query Count (Rx Thread): %d", realm.where(Ad.class).equalTo("searchId", searchCriteria.id()).count());
realm.close();
// Stuff to add records on Rx Thread
});
}
The call looks like this:
SearchController.instance().searchWithCriteriaForMore(searchCriteria)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleSubscriber<SearchController.Update>() {
...
}
Thoughts anyone? I've been trying to nail this down for a few weeks, thought it was something I was doing, but if I do the count on the main thread and pass it in, it works fine.
I'm running RxJava 1.2.7, RxAndroid 1.2.1, and Realm 3.3.1.
It feels that you have a Realm instance on RxThread somewhere which is not closed before. Since RxThread doesn't have a looper, the realm instance cannot be auto-updated. So it was locked at the version when it was created.
Realm is using ref counter internally, the getDefaultInstance() will just return the same instance you opened on the RxThread before.
The solution:
1. Find out which Realm instance was retrieved on the RxThread and close it properly. So the next time getDefaultInstance() will return a new Realm instance at the latest version data.
2. If it is intended to have a Realm instance on the RxThread, you can call realm.refresh() to manually move it to the latest data version.
Brief introduction for those not familiar with Android and/or Firebase development:
In Android development, you should always manipulate your application's views from the main thread (also called UI thread), but if your application needs to make some heavy processing, it should use a background thread, otherwise the app would seem unresponsive.
Firebase is a service that offers a way to store and sync data with a NoSQL database in the cloud. It also offers an Android SDK to manage this database. Every time this SDK is used to make an operation, like a query, Firebase avoids those threading pitfalls by making all of its heavy processing on its own internal background thread and by always calling its callbacks on the main thread.
Example:
Query postsQuery = FirebaseDatabase.getInstance().getReference("posts");
ValueEventListener postListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// This is always called on the main thread
// Get Post object and use the values to update the UI
Post post = dataSnapshot.getValue(Post.class);
// ...
}
#Override
public void onCancelled(DatabaseError databaseError) {
// Getting Post failed, log a message
printError(databaseError.toException());
// ...
}
};
postsQuery.addValueEventListener(postListener);
The actual issue I'm facing:
I'm trying to wrap Firebase's query listeners with RxJava using a method like this:
private static Observable<DataSnapshot> queryObservable(final Query query) {
return Observable.fromEmitter(emitter -> {
// This is called on the Scheduler's thread defined with .subscribeOn()
printThread("emitter");
final ValueEventListener listener = new ValueEventListener() {
#Override public void onDataChange(final DataSnapshot dataSnapshot) {
// This is always called on the main thread
printThread("onDataChange");
emitter.onNext(dataSnapshot);
}
#Override public void onCancelled(final DatabaseError databaseError) {
// This is called on the main thread too
emitter.onError(databaseError.toException());
}
};
query.addValueEventListener(listener);
emitter.setCancellation(() -> query.removeEventListener(listener));
}, Emitter.BackpressureMode.BUFFER);
}
But because the Observable is emitting items from inside the Firebase's callback (called on the main thread) any further .subscribeOn() operators are going to be ignored.
For example, calling the above method like this:
Query postsQuery = FirebaseDatabase.getInstance().getReference("posts");
queryObservable(postsQuery).doOnSubscribe(() -> printThread("onSubscribe"))
.subscribeOn(Schedulers.io())
.subscribe(dataSnapshot -> printThread("onNext"));
Would print the following:
onSubscribe Thread: RxIoScheduler-2
emitter Thread: RxIoScheduler-2
onDataChange Thread: main
onNext Thread: main
From what I understand, when Firebase's SDK calls the onDataChange() callback and switches from its own internal background thread to the main thread, it also makes the Observable emit new items on the main thread, rendering useless any .subscribeOn() operator down the stream.
The actual question:
What can I do to not only correctly wrap listeners like this into an Observable but also make them conform to the Scheduler defined by .subscribeOn()?
Thank you!
Update:
I know .observeOn() gives me the ability to process the data returned by Firebase on another thread. That's what I'm doing already, but it just isn't the point of this question. The point is: when I pass a Scheduler through .subscribeOn() I expect the upstream to conform to that Scheduler's thread but that doesn't happen when the Observable has an internal listener that is being triggered from a callback on a different thread. When that happens, I lose the .subscribeOn() guarantee.
The severity of this issue may not seem obvious at first, but what if that Observable was part of a library? What's the best practice there? Should the library enforce its clients to always call an .observeOn() after any call to that method? Should the library call an .observeOn() itself and call it a "default Scheduler"? In any of these cases the .subscribeOn() is just useless, and that doesn't seem right to me.
Just use observeOn in IO and subscribeOn in Main Thread, in that way you can manage your recieved that in MainThread and move the firebase work to a different Thread.
Remember to import rxAndroid to your gradle(Rxjava or RxJava 2):
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
Also suggest you to check as reference(or just use it) one of the next libraries:
RxJava : https://github.com/nmoskalenko/RxFirebase
RxJava 2.0: https://github.com/FrangSierra/Rx2Firebase
One of them works with RxJava and the other one with the new RC of RxJava 2.0. If you are interested of it, you can see the differences between both here.
I Had the same problem, finally I combined with Coroutines to run the listener on background.
To do that, simple add coroutine background work in your onDataChange
Kind regards
I pretty much understand the concept of subscribe (any code below subscribeOn will be performed in that particular thread) and observe (same with subscribeOn) in rxandroid/rxjava.
What I want to happen is to perform long io operation in background thread then notify the main thread if the operations is finished. To do that, I'm thinking of having a flatmap which is subscribed in Schedulers.io() then observe a subscribe in AndroidSchedulers.mainThread(), something like this:
Observable.just(1)
.subscribeOn(Schedulers.io())
.flatMap(o -> {
longIO();
return null;})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(//i want to notify user here);
This is actually performing the longIO() in a different thread, thus not blocking the main thread, my problem is, this doesn't notify the main thread that longIO() is finished, note that android doesn't allow notifying user by creating Toast or AlertDialog if not in main thread. The code doesn't seem to pass through subscribe
Note: I used just(1) even though I don't use the integer 1 because I want the method inside flatMap to be performed. If I used empty it won't go through flatMap
The return type of flatMap is Observable. If the flatMap returns a null Observable, the subscriber won't get notified. Change the return statement to return Observable.just(null);
But, it's preferred to use Observable.fromCallable() to wrap your longIO() method, so just(1) would be obsolete and code looks cleaner. Note: the return type offromCallable() isn't Observable, so the subscriber would get notified even null is returned. It would look like:
Observable.fromCallable(() -> {
longIO;
return null;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
I think that you are wrong in few things. IMO everything ABOVE subscribeOn() will be done in specific thread from thread pool. And of course everything BELOW observeOn should be pass into UI Thread.
Second thing - You cannot perform that your flatMap operator is returning null. You need to return Observable. If you don't need to pass data you can use : Observable.just(null) or Observable.never().
I think that better solution would be:
Observable.create(new Observable.OnSubscribe<Object>() {
#Override
public void call(final Subscriber<? super Object> subscriber) {
longIO();
}
})
.startWith(new Object()) //if you want to run it once
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
I'm still somewhat new to rxAndroid/rxJava so I'm having a little trouble wrapping my mind around this and if I'm doing everything correctly.
I am using SqlBrite to update a RecyclerView with rows returned from a Cursor. When a db operation is performed on the table, the observable below is responsible for re-querying the data. I want to return a CursorWrapper (ChecklistCursor), but want to make sure I am running the select * query on a worker thread. The checklistRecyclerAdapter subscribes to the ChecklistCursor observable and is responsible for managing the cursor.
The code below seems to be the only way that I am able to get query.run() to run on a worker thread and the subscription to return on the main thread.
I guess it works, but I don't believe this would be the standard way to do this. Could anyone offer a better method than using observeOn twice?
compositeSubscription.add(db.createQuery(Checklist.TABLE, Checklist.QUERY_ALL)
.observeOn(Schedulers.newThread())
.map(query -> new ChecklistCursor(query.run()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(checklistListRecyclerAdapter));
I would replace the first observeOn call with subscribeOn(Schedulers.io()), in order to perform the query operation in a background thread. Map operator will still perform the operation in the background thread, and after that, you can change with observeOn to update your adapter. I think this is the only way to do this.