Handling Executors in RxKotlin - android

I will be using thread executors to do some background work with rxkotlin, I made threadpool size fixed to 3, but my problem is during my background operation it using only one thread out of 3, which slows down my background operation
Executor class
class ThreadExe : Executor{
companion object {
private const val THREAD_POOL_SIZE = 3
}
private val executor: Executor =
Executors.newFixedThreadPool(THREAD_POOL_SIZE)
override fun execute(runnable: Runnable) {
executor.execute(runnable)
}
}
The above is my executor class responsible for creating thread.
I will be calling my background task like below
getSomeDataFromNetworkProcessWithDB()
.subscribeOn(Schedulers.from(ThreadExe()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe()
fun getSomeDataFromNetworkProcessWithDB() {
Observable.fromIteratable(someDataList())
.flatMap {
getSomeNetworkData()
}
.flatMap {
doSomeDbOperation()
}
}
my problem with the above code is all these network and db operation is working sequentially with the same thread, since we have give the threadpool of size 3 it must send the 3 network request parallely, but the request is going sequentially
Can anyone help me out this problem ?

If you want individual operation to run on different thread try this:
getSomeDataFromNetworkProcessWithDB(Schedulers.from(ThreadExe()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe()
fun getSomeDataFromNetworkProcessWithDB(scheduler: Scheduler): Observable<Data> {
return Observable.fromIterable(someDataList())
.flatMap {
getSomeNetworkData().subscribeOn(scheduler)
}
.flatMap {
doSomeDbOperation().subscribeOn(scheduler)
}
.subscribeOn(scheduler) // optional, if you want fromIterable(), someDataList() to run on this scheduler.
}

Related

RxJava — emit on subscribeOn() thread

I have the following code:
Single.create { emitter ->
// I/O thread here
ThirdPartySDK.doSomeAction {
// Main thread here
emitter.onSuccess(someValue)
}
}
.flatMap {
someOtherSingle(it) // Executes on main thread
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({},{})
The ThirdPartySDK.doSomeAction callback posts on main thread, so the emitter will emit on the main thread too, not on the subscribe thread (and if I have some network interactions further in the flatMap, chain will fail).
If I add observeOn(Schedulers.io()) after the first Single, it switches to the correct thread, but is there any way to emit on right thread? I can't modify ThirdPartySDK behaviour.
subscribeOn
The subscribeActual lambda will be invoked on given scheduler
observeOn
Switch thread to given scheduler. Every upstream-onNext call will be called from an ObserveOn-Scheduler-Thread
As you already said, subscribeOn will only invoke the subscribeActual method call on subscribe on given Scheduler-Thread. This does not mean, that the downstream emit will be on the same thread. In your case the onSuccess emit will be called from a different thread (e.g. Database/ Http-ThreadPool etc.).
onSuccess will be called from a unknown thread (in your case main thread). The downstream call will be called from the main-thread. Therefore flatMap is called from the main-thread. Network-calls on the main-thread in the flatMap will probably fail, because it is not allowed to "block" the main-thread.
How to solve this issue?
Just place a observeOn after the Single#create. The main-thread calls onSucess. The observeOn-subscriber will get called from the main-thread. The observeOn-subscriber re-directs onSuccess downstream-call (e.g. flatMap) to given ObserveOn-Scheduler-Thread. Therefore it is given, that flatMap is called from a non main-loop thread.
Example:
#Test
fun wurst() {
val thirdPartySDKImpl = ThirdPartySDKImpl()
Single.create<String> { emitter ->
thirdPartySDKImpl.doSomeAction {
emitter.onSuccess(it)
}
}
// .subscribeOn(Schedulers.computation())
// move emit from unknown thread to computation thread
.observeOn(Schedulers.computation())
// Single.just will be subscribe from a computation thread
.flatMap { Single.just(123) }
// move onSucess/ onError emit from computation thread to main-thread
.observeOn(AndroidSchedulers.mainThread())
// subscribe onNext / onError will be called from the main-android-thread
.subscribe({}, {})
}
interface ThirdPartySDK {
fun doSomeAction(callback: (v: String) -> Unit)
}
class ThirdPartySDKImpl : ThirdPartySDK {
override fun doSomeAction(callback: (v: String) -> Unit) {
// <- impl-detail ->
callback("whatever")
}
}
NOTE: You do not need a subscribeOn, if the create-lambda does not block or does some cpu heavy stuff. If it only subscribes to a callback, which will be called from a different thread, you do not need subscribeOn.
but is there any way to emit on right thread?
You should not use any concurrency in operators. You would think, you could just do something like:
Single.create<String> { emitter ->
thirdPartySDKImpl.doSomeAction {
Schedulers.io().scheduleDirect {
emitter.onSuccess(it)
}
}
}
But this is not recommended, because you could break the serialized onNext contract^1. This example would make sure, that the onSucess downstream call would happen on expected thread, but cancellation/ unsubscription is not handled and there might be other pitfalls.
If you have a non reactive API and you want to enforce some threading-model I would suggest to wrap the sync. API with an async one and provide proper observeOn/ subscribeOn operators. Later on only use the async API.
interface ThirdPartySDKAsync {
fun doSomeAction(): Single<String>
}
class ThirdPartySDKAsyncImpl(private val sdk: ThirdPartySDK, private val scheduler: Scheduler) :
ThirdPartySDKAsync {
override fun doSomeAction(): Single<String> {
return Single.create<String> { emitter ->
sdk.doSomeAction {
emitter.onSuccess(it)
}
}.observeOn(scheduler)
}
}
Further reading: https://tomstechnicalblog.blogspot.com/2016/02/rxjava-understanding-observeon-and.html
^1 Only one thread a time is allowed to call onNext/onSuccess/onError/onComplete

Chain completation sync and async

I have a list of completables that by default I run them one after one with concat/andThen operators.
Sometimes I want some part of the completables to run in parallel and after everything complete continue to the next completable in the list.
I tried to achieve that with this code:
var completable =
getAsyncCompletables()?.let {
it
} ?: run {
completables.removeAt(0).getCompletable()
}
while (completables.isNotEmpty()) {
val nextCompletable = getAsyncCompletables()?.let {
it
} ?: run {
completables.removeAt(0).getCompletable()
}
completable = nextCompletable.startWith(completable)
}
completable
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe()
I use this code to detect the async completables:
private fun getAsyncCompletables(): Completable? {
if (completables.size < 2 || !completables[1].async) {
return null
}
var completable = completables.removeAt(0).getCompletable()
while (completables.isNotEmpty() && completables[0].async) {
completable = completable.mergeWith(completables.removeAt(0).getCompletable())
}
return completable
}
All works fine, except one thing, the last completable not triggered althought I used "startWith".
I also tried "concatWith" and "andThen",but same result.
It is a bit difficult to answer without seeing more of your code, specifically what async does and what the data structure is for completables. However, the answer you are looking for is most likely similar regardless of these values. You will probably want to use Completable.merge(...) or Completable.mergeArray(...).
As per the documentation:
/**
* Returns a Completable instance that subscribes to all sources at once and
* completes only when all source Completables complete or one of them emits an error.
* ...
*/
In order to achieve the parallel execution, you will need to call subscribeOn each of your Completables in the list/array/set with a new thread. This can be done with Schedulers.newThread() or from a shared pool like Schedulers.io().
I ran a test just to be sure. Here is the code.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
val completeOne = Completable.fromAction {
Timber.d("Completable #1 running on ${Thread.currentThread().name}")
}
val completeTwo = Completable.fromAction {
Timber.d("Completable #2 running on ${Thread.currentThread().name}")
}
val completeThree = Completable.fromAction {
Timber.d("Completable #3 running on ${Thread.currentThread().name}")
}
val completables = listOf(completeOne, completeTwo, completeThree).map { CompletableWrapper(it) }
val asyncCompletables = completables
.asSequence()
.filter { it.async }
.map { it.getCompletable().subscribeOn(Schedulers.io()) }
.toList()
Completable.merge(asyncCompletables)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
Timber.i("Completed all completables")
}, Timber::e)
}
class CompletableWrapper(
private val completable: Completable,
val async: Boolean = true
) {
fun getCompletable() = completable
}
And here is the output.
D/MainActivity$onCreate$completeThree: Completable #3 running on RxCachedThreadScheduler-3
D/MainActivity$onCreate$completeTwo: Completable #2 running on RxCachedThreadScheduler-2
D/MainActivity$onCreate$completeOne: Completable #1 running on RxCachedThreadScheduler-1
I/MainActivity$onCreate: Completed all completables
As you can see, it runs each completable on a new thread from the pool and only calls completed all after each completable has finished.
See here for the documentation on Completable.merge/mergeArray.

java.lang.IllegalStateException: Cannot invoke observeForever on a background thread

Can someone help me find where I am going wrong here. I need to continously observer network data and update the UI whenever there is a data change from the Worker. Please note that this was working before upgrading to androidx.
Here is a Worker class.
class TestWorker(val context: Context, val params: WorkerParameters): Worker(context, params){
override fun doWork(): Result {
Log.d(TAG, "doWork called")
val networkDataSource = Injector.provideNetworkDataSource(context)
networkDataSource.fetchData(false)
return Worker.Result.SUCCESS
}
companion object {
private const val TAG = "MY_WORKER"
}
}
Which is called as follows:
fun scheduleRecurringFetchDataSync() {
Log.d("FETCH_SCHEDULER", "Scheduling started")
val fetchWork = PeriodicWorkRequest.Builder(TestWorker::class.java, 1, TimeUnit.MINUTES)
.setConstraints(constraints())
.build()
WorkManager.getInstance().enqueue(fetchWork)
}
private fun constraints(): Constraints{
return Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
}
I also have a UserDao and UserRepository to fetch and store data. I am observing the network data in the UserRepository as follows:
class UserRepository (
private val userDao: UserDao,
private val networkDataSource: NetworkDataSource,
private val appExecutors: AppExecutors){
init {
val networkData= networkDataSource.downloadedData
networkData.observeForever { newData->
appExecutors.diskIO().execute {
userDao.insert(newData.user)
}
}}
Can someone help me locate where I am going wrong. This is giving me error as follows:
java.lang.IllegalStateException: Cannot invoke observeForever on a background thread
at androidx.lifecycle.LiveData.assertMainThread(LiveData.java:443)
at androidx.lifecycle.LiveData.observeForever(LiveData.java:204)
at com.example.app.data.repo.UserRepository.<init>(UserRepository.kt:17)
at com.example.app.data.repo.UserRepository$Companion.getInstance(UserRepository.kt:79)
Change this:
networkData.observeForever { newData->
appExecutors.diskIO().execute {
userDao.insert(newData.user)
}
}
To:
Variant B (with coroutines):
GlobalScope.launch(Dispatchers.Main) { networkData.observerForever { /*..*/ } }
But be aware, the usage of GlobalScope is not recommended: https://stackoverflow.com/a/54351785/1185087
Variant A (without coroutines):
Handler(Looper.getMainLooper()).post { networkData.observeForever{ /*..*/ } }
Explanation
Normally observe(..) and observeForever(..) should be called from the main thread because their callbacks (Observer<T>.onChanged(T t)) often change the UI which is only possible in the main thread. That's the reason why android checks if the call of the observe functions is done by the main thread.
In your case UserRepository.init{} is called by a background thread, so the exception is thrown. To switch back to the main thread you can use one of the above variants. But be aware the code inside of your observe callback is executed by the main thread, too. Any expensive processing inside this callback will freeze your UI!
In another solution, you can call it from main dispatcher as
GlobalScope.launch(Dispatchers.Main) {
// your code here...
}
Additionally to the nice and detailled answer from #user1185087 here is a solution if you're using RxJava in your project. It's maybe not that short, but if you already use RxJava in your project, it's an elegant way to switch to the required thread (in this case the Android's UI thread via .observeOn(AndroidSchedulers.mainThread())).
Observable.just(workManager.getStatusById(workRequest.getId()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(status -> status.observeForever(workStatus -> {
// Handling result on UI thread
}), err -> Log.e(TAG, err.getMessage(), err));
In my case I was testing liveData and I forgot to add the InstantTaskExecutorRule().
#RunWith(AndroidJUnit4::class)
class UserDaoTest {
#get:Rule // <----
var instantExecutorRule = InstantTaskExecutorRule() // <----
....
}
Don't forget to add the library to the project.
testImplementation"androidx.arch.core:core-testing:2.1.0" // unit tests
androidTestImplementation "androidx.arch.core:core-testing:2.1.0"//instrumentation tests
Here is what I did in my Java code to make it work
// the LiveData query
LiveData<List<Calf>> calfLiveDataList = getCalfDao().getAllCalves();
Handler handler = new Handler(Looper.getMainLooper()); //This is the main thread
handler.post(new Runnable() { //task to run on main thread
#Override
public void run() {
calfLiveDataList.observeForever(observer);
}
}
);
Ignore my naming conventions, my app is for cow farmers.

Worker queue using RxJava

I want to create worker queue using RxJava: I have a single thread doing some work, and I want to guarantee that no other job will be executed until we have finished/failed the current job.
My solution is simply to block the observable and wait for the result:
fun foo() : Observable<Foo> {
return Observable.unsafeCreate { subscriber ->
handlerThread.post {
val answer = object.performSomeJob(whatever)
.flatMap { object.performAnotherJob(whatever) }
.flatMap { object.performLastJob(whatever) }
.blockingFirst()
subscriber.onNext(answer)
subscriber.onComplete()
}
}
}
You may argue that there is no need to use RxJava since everything's synchronous. That's true for this particular method, but:
I want to avoid 'callback hell': there are three methods, each of which is taking callback and I use RxJava to chain them
I use Rx further on in the caller method.
I know that blocking is generally considered as an anti-pattern, so can I do better in my case?
you can use concat to perform work sequentially on some thread:
fun foo(): Observable<Foo> {
return performSomeJob(whatever)
.concatMap { performAnotherJob(whatever) }
.concatMap { performLastJob(whatever) }
.subscribeOn(Schedulers.newThread())
}
You can schedule all your work on one single-threaded Scheduler such as
#NonNull
public static Scheduler single()
Returns a default, shared, single-thread-backed Scheduler instance for work requiring strongly-sequential execution on the same background thread.
fun foo(): Observable<Foo> =
Observable.fromCallable { object.performSomeJob(whatever) }
.subscribeOn(Schedulers.single())
.observeOn(Schedulers.single())
.flatMap { object.performAnotherJob(whatever) }
.flatMap { object.performLastJob(whatever) }

Kotlin Coroutines the right way in Android

I'm trying to update a list inside the adapter using async, I can see there is too much boilerplate.
Is it the right way to use Kotlin Coroutines?
can this be optimized more?
fun loadListOfMediaInAsync() = async(CommonPool) {
try {
//Long running task
adapter.listOfMediaItems.addAll(resources.getAllTracks())
runOnUiThread {
adapter.notifyDataSetChanged()
progress.dismiss()
}
} catch (e: Exception) {
e.printStackTrace()
runOnUiThread {progress.dismiss()}
} catch (o: OutOfMemoryError) {
o.printStackTrace()
runOnUiThread {progress.dismiss()}
}
}
After struggling with this question for days, I think the most simple and clear async-await pattern for Android activities using Kotlin is:
override fun onCreate(savedInstanceState: Bundle?) {
//...
loadDataAsync(); //"Fire-and-forget"
}
fun loadDataAsync() = async(UI) {
try {
//Turn on busy indicator.
val job = async(CommonPool) {
//We're on a background thread here.
//Execute blocking calls, such as retrofit call.execute().body() + caching.
}
job.await();
//We're back on the main thread here.
//Update UI controls such as RecyclerView adapter data.
}
catch (e: Exception) {
}
finally {
//Turn off busy indicator.
}
}
The only Gradle dependencies for coroutines are: kotlin-stdlib-jre7, kotlinx-coroutines-android.
Note: Use job.await() instead of job.join() because await() rethrows exceptions, but join() does not. If you use join() you will need to check job.isCompletedExceptionally after the job completes.
To start concurrent retrofit calls, you can do this:
val jobA = async(CommonPool) { /* Blocking call A */ };
val jobB = async(CommonPool) { /* Blocking call B */ };
jobA.await();
jobB.await();
Or:
val jobs = arrayListOf<Deferred<Unit>>();
jobs += async(CommonPool) { /* Blocking call A */ };
jobs += async(CommonPool) { /* Blocking call B */ };
jobs.forEach { it.await(); };
How to launch a coroutine
In the kotlinx.coroutines library you can start new coroutine using either launch or async function.
Conceptually, async is just like launch. It starts a separate coroutine which is a light-weight thread that works concurrently with all the other coroutines.
The difference is that launch returns a Job and does not carry any resulting value, while async returns a Deferred - a light-weight non-blocking future that represents a promise to provide a result later. You can use .await() on a deferred value to get its eventual result, but Deferred is also a Job, so you can cancel it if needed.
Coroutine context
In Android we usually use two context:
uiContext to dispatch execution onto the Android main UI thread (for the parent coroutine).
bgContext to dispatch execution in background thread (for the child coroutines).
Example
//dispatches execution onto the Android main UI thread
private val uiContext: CoroutineContext = UI
//represents a common pool of shared threads as the coroutine dispatcher
private val bgContext: CoroutineContext = CommonPool
In following example we are going to use CommonPool for bgContext which limit the number of threads running in parallel to the value of Runtime.getRuntime.availableProcessors()-1. So if the coroutine task is scheduled, but all cores are occupied, it will be queued.
You may want to consider using newFixedThreadPoolContext or your own implementation of cached thread pool.
launch + async (execute task)
private fun loadData() = launch(uiContext) {
view.showLoading() // ui thread
val task = async(bgContext) { dataProvider.loadData("Task") }
val result = task.await() // non ui thread, suspend until finished
view.showData(result) // ui thread
}
launch + async + async (execute two tasks sequentially)
Note: task1 and task2 are executed sequentially.
private fun loadData() = launch(uiContext) {
view.showLoading() // ui thread
// non ui thread, suspend until task is finished
val result1 = async(bgContext) { dataProvider.loadData("Task 1") }.await()
// non ui thread, suspend until task is finished
val result2 = async(bgContext) { dataProvider.loadData("Task 2") }.await()
val result = "$result1 $result2" // ui thread
view.showData(result) // ui thread
}
launch + async + async (execute two tasks parallel)
Note: task1 and task2 are executed in parallel.
private fun loadData() = launch(uiContext) {
view.showLoading() // ui thread
val task1 = async(bgContext) { dataProvider.loadData("Task 1") }
val task2 = async(bgContext) { dataProvider.loadData("Task 2") }
val result = "${task1.await()} ${task2.await()}" // non ui thread, suspend until finished
view.showData(result) // ui thread
}
How to cancel a coroutine
The function loadData returns a Job object which may be cancelled. When the parent coroutine is cancelled, all its children are recursively cancelled, too.
If the stopPresenting function was called while dataProvider.loadData was still in progress, the function view.showData will never be called.
var job: Job? = null
fun startPresenting() {
job = loadData()
}
fun stopPresenting() {
job?.cancel()
}
private fun loadData() = launch(uiContext) {
view.showLoading() // ui thread
val task = async(bgContext) { dataProvider.loadData("Task") }
val result = task.await() // non ui thread, suspend until finished
view.showData(result) // ui thread
}
The complete answer is available in my article Android Coroutine Recipes
I think you can get rid of runOnUiThread { ... } by using UI context for Android applications instead of CommonPool.
The UI context is provided by the kotlinx-coroutines-android module.
We also have another option. if we use Anko library , then it looks like this
doAsync {
// Call all operation related to network or other ui blocking operations here.
uiThread {
// perform all ui related operation here
}
}
Add dependency for Anko in your app gradle like this.
implementation "org.jetbrains.anko:anko:0.10.5"
Like sdeff said, if you use the UI context, the code inside that coroutine will run on UI thread by default. And, if you need to run an instruction on another thread you can use run(CommonPool) {}
Furthermore, if you don't need to return nothing from the method, you can use the function launch(UI) instead of async(UI) (the former will return a Job and the latter a Deferred<Unit>).
An example could be:
fun loadListOfMediaInAsync() = launch(UI) {
try {
withContext(CommonPool) { //The coroutine is suspended until run() ends
adapter.listOfMediaItems.addAll(resources.getAllTracks())
}
adapter.notifyDataSetChanged()
} catch(e: Exception) {
e.printStackTrace()
} catch(o: OutOfMemoryError) {
o.printStackTrace()
} finally {
progress.dismiss()
}
}
If you need more help I recommend you to read the main guide of kotlinx.coroutines and, in addition, the guide of coroutines + UI
If you want to return some thing from background thread use async
launch(UI) {
val result = async(CommonPool) {
//do long running operation
}.await()
//do stuff on UI thread
view.setText(result)
}
If background thread is not returning anything
launch(UI) {
launch(CommonPool) {
//do long running operation
}.await()
//do stuff on UI thread
}
All the above answers are right, but I was having a hard time finding the right import for the UI from kotlinx.coroutines, it was conflicting with UI from Anko.
Its
import kotlinx.coroutines.experimental.android.UI
Here's the right way to use Kotlin Coroutines. Coroutine scope simply suspends the current coroutine until all child coroutines have finished their execution. This example explicitly shows us how child coroutine works within parent coroutine.
An example with explanations:
fun main() = blockingMethod { // coroutine scope
launch {
delay(2000L) // suspends the current coroutine for 2 seconds
println("Tasks from some blockingMethod")
}
coroutineScope { // creates a new coroutine scope
launch {
delay(3000L) // suspends this coroutine for 3 seconds
println("Task from nested launch")
}
delay(1000L)
println("Task from coroutine scope") // this line will be printed before nested launch
}
println("Coroutine scope is over") // but this line isn't printed until nested launch completes
}
Hope this helps.
Please find attached the implementation for a remote API call with Kotlin Coroutines & Retrofit library.
import android.view.View
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.test.nyt_most_viewed.NYTApp
import com.test.nyt_most_viewed.data.local.PreferenceHelper
import com.test.nyt_most_viewed.data.model.NytAPI
import com.test.nyt_most_viewed.data.model.response.reviews.ResultsItem
import kotlinx.coroutines.*
import javax.inject.Inject
class MoviesReviewViewModel #Inject constructor(
private val nytAPI: NytAPI,
private val nytApp: NYTApp,
appPreference: PreferenceHelper
) : ViewModel() {
val moviesReviewsResponse: MutableLiveData<List<ResultsItem>> = MutableLiveData()
val message: MutableLiveData<String> = MutableLiveData()
val loaderProgressVisibility: MutableLiveData<Int> = MutableLiveData()
val coroutineJobs = mutableListOf<Job>()
override fun onCleared() {
super.onCleared()
coroutineJobs.forEach {
it.cancel()
}
}
// You will call this method from your activity/Fragment
fun getMoviesReviewWithCoroutine() {
viewModelScope.launch(Dispatchers.Main + handler) {
// Update your UI
showLoadingUI()
val deferredResult = async(Dispatchers.IO) {
return#async nytAPI.getMoviesReviewWithCoroutine("full-time")
}
val moviesReviewsResponse = deferredResult.await()
this#MoviesReviewViewModel.moviesReviewsResponse.value = moviesReviewsResponse.results
// Update your UI
resetLoadingUI()
}
}
val handler = CoroutineExceptionHandler { _, exception ->
onMoviesReviewFailure(exception)
}
/*Handle failure case*/
private fun onMoviesReviewFailure(throwable: Throwable) {
resetLoadingUI()
Log.d("MOVIES-REVIEWS-ERROR", throwable.toString())
}
private fun showLoadingUI() {
setLoaderVisibility(View.VISIBLE)
setMessage(STATES.INITIALIZED)
}
private fun resetLoadingUI() {
setMessage(STATES.DONE)
setLoaderVisibility(View.GONE)
}
private fun setMessage(states: STATES) {
message.value = states.name
}
private fun setLoaderVisibility(visibility: Int) {
loaderProgressVisibility.value = visibility
}
enum class STATES {
INITIALIZED,
DONE
}
}

Categories

Resources