Perform work on a different thread - android

I execute a Realm query,
then I need to perform a time consuming mapping of the result of the Realm query, consequently it needs to be done on a worker thread.
Finally I need the results on the main thread (because they update the UI).
But the following code (understandably) gives me an exception: "Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created."
val defaultInstance = Realm.getDefaultInstance()
val subscription = defaultInstance
.where(Bar::class.java)
.equalTo("foo", true)
.findAllAsync()
.asObservable()
.filter { it.isLoaded && it.isValid }
.map { defaultInstance.copyFromRealm(it) }
// The following is a time consuming task, I need to perform it on another thread
.observeOn(workerScheduler)
.map { someComplexMapping(it) }
// but the results are needed on the main thread (because it updates the UI)
.subscribeOn(mainThreadScheduler)
.subscribe(observer)
Please how do I achieve what I need?

val defaultInstance = Realm.getDefaultInstance()
val subscription = defaultInstance
.where(Bar::class.java)
.equalTo("foo", true)
.findAllAsync()
.asObservable()
.subscribeOn(mainThreadScheduler)
.filter { it.isLoaded && it.isValid }
.observeOn(Schedulers.io())
.map {
Realm.getDefaultInstance().use {
//it.refresh() // shouldn't be needed
it.copyFromRealm(it.where(Bar::class.java).equalTo("foo", true).findAll())
}
}
// The following is a time consuming task, I need to perform it on another thread
.observeOn(workerScheduler)
.map { someComplexMapping(it) }
// but the results are needed on the main thread (because it updates the UI)
.observeOn(mainThreadScheduler)
.subscribe(observer)

Create an Observable before the Realm query
and put it into the UI thread with .observeOn(AndroidScheduler.mainThread()). Run the Realm query and the other stuffs like you did before.

Related

Android multithreading - coroutine and UI thread

I am new to multithreading and looking for solution for this problem.
I am launching a method in coroutine which updates data in my database and if it is updated I would like to update the UI for users. How to this? I cannot put runOnUiThread inside a coroutine. Is there some type of magic like -> when coroutine finished -> then -> runOnUi?
Greetings
You don't need to call runOnUiThread as the coroutine will have the main dispatcher as the context.
Let's say you have this helper function to offload work to the I/O thread.
suspend fun <T> withIO(block: suspend CoroutineScope.() -> T) = withContext(Dispatchers.IO, block)
If you are using a ViewModel, then you can call it like this
viewModelScope.launch {
val result = withIO {
// You are on IO thread here.
update your database
}
// The block will be suspended until the above task is done.
// You are on UI thread now.
// Update your UI.
}
If you are not using a ViewModel, you can also use
withContext(Disptachers.Main) {
val result = withIO {
// You are on IO thread
}
// You are back on the main thread with the result from the task
}
Coroutine are task that work on different thread.
What you really want is wating for changes in database. Coroutine in this idea could work for insert data in db, but listening part is role of ViewModel pattern.
I recently answer similar question to yours:
AutocompleteTextView with room
More specific could be this answer from another user:
Wait until Kotlin coroutine finishes in onCreateView()
So the basic problem is to jumping back to main thread after co-routine finishes
this can be done multiple ways
using launch(Dispatcher.Main)
from main thread init co-routine
something like this
//launches coroutine running on main thread
GlobalScope.launch(Dispatchers.Main) {
updateDb()
}
suspend fun updateDb(){
//runs on worker thread and returns data
val value = withContext(Dispatchers.IO){
saveDataInDb();
}
//runs back on main thread
updateUI(value);
}
However global scope should not be used
You can read about that here https://medium.com/#elizarov/the-reason-to-avoid-globalscope-835337445abc
using async await
suspend fun saveInDb() {
val value = GlobalScope.async {
delay(1000)
println("thread running on [${Thread.currentThread().name}]")
10
}
println("value = ${value.await()} thread running on [${Thread.currentThread().name}]")
}
output:
thread running on [DefaultDispatcher-worker-1]
value = 10 thread running on [main]
thread running on [main]

Realm access from incorrect thread on compositeDisposable clear

I am adding all my observer that are subscribed in an activity are added to CompositeDisposable.
OnStop of activity is calling mCompositeDisposable.clear() which creates below crash log.
As CompositeDisposable.clear calls onDispose please find below code does to realm
Single.create(...).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
mCompositeDisposable.add(observer);
creats realm here in Schedulers.io()
Single<RealmList<T>> source ...
source .doOnDispose(() -> {
if (mRealm == null) {
return;
}
if (Looper.myLooper() != null) {
mRealm.removeAllChangeListeners();
}
if (!mRealm.isClosed()) {
mRealm.close();
}
mRealm = null;
}
Giving the below crash logs
java.lang.IllegalStateException: Realm access from incorrect thread.
Realm access from incorrect thread.
Realm objects can only be accessed on the thread they were created.
at io.realm.BaseRealm.JN(SourceFile:438)
at io.realm.BaseRealm.removeAllListeners(SourceFile:263)
at io.realm.Realm.removeAllChangeListeners(SourceFile:1399)
...
at io.reactivex.internal.operators.single.SingleDoOnDispose$DoOnDisposeObserver.dispose(SourceFile:60)
at io.reactivex.internal.operators.single.SingleDoFinally$DoFinallyObserver.dispose(SourceFile:85)
at io.reactivex.internal.disposables.DisposableHelper.aq(SourceFile:124)
at io.reactivex.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.dispose(SourceFile:78)
at io.reactivex.internal.disposables.DisposableHelper.aq(SourceFile:124)
at io.reactivex.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.dispose(SourceFile:78)
at io.reactivex.internal.disposables.DisposableHelper.aq(SourceFile:124)
at io.reactivex.internal.operators.single.SingleObserveOn$ObserveOnSingleObserver.dispose(SourceFile:87)
at io.reactivex.internal.disposables.DisposableHelper.aq(SourceFile:124)
at io.reactivex.observers.DisposableObserver.dispose(SourceFile:91)
at io.reactivex.disposables.CompositeDisposable.a(SourceFile:240)
at io.reactivex.disposables.CompositeDisposable.clear(SourceFile:206)
Tried with .unsubscribeOn(Schedulers.io()) but didnt worked
creats realm here in Schedulers.io()
You're creating instance of Realm in mainThread then asking Realm to operate (e.g. load data) in io thread which is impossible in Realm by design. Example by developers of Realm states few points to keep in mind here https://github.com/realm/realm-java/tree/master/examples/rxJavaExample.
Implementing 1st and 4th points into your code snippet we will get us proper solution. Note that method findAllAsync() will do everything asynchronously for you no need to create io thread by using RxJava
Single<RealmList<T>> realmResults = realm.where(....)
.equalTo(...)
.findAllAsync()
.asFlowable()
.map { realmResult->
val realmList = RealmList<T>()
realmList.addAll(realmResult.toList())
return realmList
}
.firstOrError()
realm.close()

Calling a method in doAsync results - too much work on main thread

This is my code which does some background work
fun getAllArtists(): LiveData<List<Artist>> {
val artistListLiveData = MutableLiveData<List<Artist>>()
doAsync {
val artistList = MusicGenerator.getAllArtists()
onComplete {
getArtistInfo(artistList)
artistListLiveData.value = artistList
}
}
return artistListLiveData
}
On completion I make a network call to get Artist Info
private fun getArtistInfo(artistList: List<Artist>) {
artistList.forEach {
val url = "http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&api_key=API_KEY&format=json"
.plus("&artist=")
.plus(it.artistName)
val artistInfoList: MutableList<ArtistInfo> = ArrayList()
apiService.getArtistImage(url)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe({ result ->
info { result.toString() }
}, { error ->
error.printStackTrace()
})
verbose { artistInfoList.size }
}
}
However, I'm making sure that the network call is in the background thread and results are on the main thread.
But there is jank in the UI, and the logcat says too much work being done on the main thread. I don't get it, what am I doing wrong here?
I suspect you are creating too many threads. io() is unbounded, and computation is based on the processor cores. Since you are doing io, you should be using io(), but also need to take care to not blast a ton of requests at the same time. You can use a Flowable.flatMap to iterate through your list instead of foreach. The key here is to specify a value for the max concurrency to flatMap. Below, I have set it to 4, but you can play around with the number to see what gives you a good result for max requests inflight without creating jank. Also, since we are using flatMap, I moved your subscribe outside the loop to process the stream of results coming from getArtistImage. It is not clear what you are doing with artistInfoList from your code snippet, so I have left it off, but you can use the following as a guide --
private fun getArtistInfo(artistList: List<Artist>) {
Flowable.fromIterable(artistList).flatMap({
val url = "http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&api_key=API_KEY&format=json"
.plus("&artist=")
.plus(it.artistName)
getArtistImage(url)
.subscribeOn(Schedulers.io())
}, 4)
.subscribe({ result ->
info { result.toString() }
}, { error ->
error.printStackTrace()
})
}

Android Kotlin Realm Proper Way of Query+ Return Unmanaged Items on Bg Thread

What is the proper way of querying and returning an unmanaged result of items with realm, everything in the background thread?. I'm using somethibf like this:
return Observable.just(1)
.subscribeOn(Schedulers.io())
.map {
val realm = Realm.getDefaultInstance()
val results = realm.where(ItemRealm::class.java)
.equalTo("sent", false).findAll()
realm to results
}
.map {
val (realm, results) = it
val unManagedResults = realm.copyFromRealm(results)
realm.close()
unManagedResults
}
}
And then chaining this observable with another one that will post the results to a server.
The solution working, although is a bit ugly on this aspects:
No proper way of wrapping the realmQuery in an observable, because
there is no way of opening a realInstance in a background thread without this kind of cheat (at least that i know about), so i need to use this fake
observable Observable.just(1).
Not the best place to open and close Realm instances, inside first and second map
I don't know if it is guaranteed that the realm instance is closed after all the items have been copied.
So what is the proper way of Query and Return unmanaged results on the background thread (some context, i need this to send the results to a server, in the background and as this task is totally independent from my app current data flow, so it should be off the main thread).
Suggested Version:
return Observable.fromCallable {
Realm.getDefaultInstance().use { realm ->
realm.copyFromRealm(
realm.where(ItemRealm::class.java)
.equalTo(ItemRealm.FIELD_SEND, false).findAll()
)
}
}
This is how you would turn your Realm objects unmanaged:
return Observable.defer(() -> {
try(Realm realm = Realm.getDefaultInstance()) {
return Observable.just(
realm.copyFromRealm(
realm.where(ItemRealm.class).equalTo("sent", false).findAll()
)
);
}
}).subscribeOn(Schedulers.io());
Although this answer is Java, the Kotlin answer is just half step away.

Realm Proper way of Copy Object In Transaction with RxJava

Currently, when copying a value to realm, i do the following:
public void addToRealm(Home item, RealmChangeListener<E> listener) {
realm.executeTransaction((Realm realm1) ->
realm1.copyToRealm(item).addChangeListener<Home>(listener));
}
And then i can access the newly added object inside the listener. What is the proper RxJava way of accomplishing the same? The observable must return
Observable<Home>, which is the realmCopy not the original object. Can any1 please provide a sample?
Managed to get it working by doing this, altought im not sure it is the best approach... What is the recommended approach?
return Observable.just(homeItem)
.map { (HomeItem homeItem) ->
return AnotherHomeItem(homeItem.xxx, homeItem.yyy)
}
.flatMap { (AnotherHomeItem anotherItem) ->
realm.beginTransaction()
val newItem = realm.copyToRealm(anotherItem).asObservable< AnotherHomeItem >()
realm.commitTransaction()
return newItem
}
.filter {
return it.isLoaded
}
You should write to the Realm on a background thread, and observe with a different subscription on the UI thread.
You persist with one subscription on the background thread:
public Subscription downloadObjectsFromNetwork() {
return objectApi.getObjects()
.subscribeOn(Schedulers.io())
.subscribe(response -> {
try(Realm realmInstance = Realm.getDefaultInstance()) {
realmInstance.executeTransaction(realm -> realm.insertOrUpdate(response.objects));
}
});
}
And you read with asObservable() on the UI thread:
public Subscription readFromRealm() {
return realm.where(SomeObject.class)
.findAllAsync()
.asObservable()
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread())
.filter(RealmResults::isLoaded)
.subscribe(objects -> adapter.updateData(objects));
}
Using Realm with RxJava
For queries, Realm provides the realmResults.asObservable() method. Observing results is only possible on looper threads (typically the UI thread).
For this to work, your configuration must contain the following
realmConfiguration = new RealmConfiguration.Builder(context) //
.rxFactory(new RealmObservableFactory()) //
//...
.build();
Afterwards, you can use your results as an observable.
Observable<RealmResults<SomeObject>> observable = results.asObservable();
For asynchronous queries, you should filter the results by isLoaded(), so that you receive an event only when the query has been executed. This filter() is not needed for synchronous queries (isLoaded() always returns true on sync queries).
Subscription subscription = RxTextView.textChanges(editText).switchMap(charSequence ->
realm.where(SomeObject.class)
.contains("searchField", charSequence.toString(), Case.INSENSITIVE)
.findAllAsync()
.asObservable())
.filter(RealmResults::isLoaded) //
.subscribe(objects -> adapter.updateData(objects));
For writes, you should either use the executeTransactionAsync() method, or open a Realm instance on the background thread, execute the transaction synchronously, then close the Realm instance.
public Subscription loadObjectsFromNetwork() {
return objectApi.getObjects()
.subscribeOn(Schedulers.io())
.subscribe(response -> {
try(Realm realmInstance = Realm.getDefaultInstance()) {
realmInstance.executeTransaction(realm -> realm.insertOrUpdate(response.objects));
}
});
}

Categories

Resources