I am trying to achieve the following. I load a list of objects I want to get values to put in a list later.
First I gather all the values into an array (to mountain order) using flatmap and then when everything is done I populate an adapter.
The thing I am unable to do is to repeat the operation ever xxx seconds. I understand its done using an interval. Still I get no result at all, or only none repeating one result.
Here’s my code:
Observable.fromIterable(URLList)
.concatMap(url -> standartRequest(App.getInstance().getApi().getService().getData(currency.getUrl())))
.retry(Constants.RETRY_COUNT)
.timeout(Constants.TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::success, this::error, this::valuesRetrieved);
recyclerView = ((CurrencyListFragment) controller).getRecyclerView();
LinearLayoutManager linearManager = new LinearLayoutManager(controller.getContext());
recyclerView.setLayoutManager(linearManager);
}
private void valuesRetrieved() {
listAdapter adapter = new listAdapter(valuesFromResponse);
recyclerView.setAdapter(adapter);
}
private void success(Object response) {
valuesFromResponse.add(response);
}
Where do I put
.interval(5, TimeUnit.SECONDS).timeInterval()
Well actually, you do not put interval anywhere, for repeating the operation every x interval, you should use repeat operator variant called repeatWhen where you can provide your interval logic in this way:
.repeatWhen(completed -> completed.delay(5, TimeUnit.SECONDS))
repeatWhen() will hand to you an Observable that transform your source Observable onCompleted() events as onNext() (with void), you should return Observable that emits onNext() which signals to resubscribe to your source Observable - meaning repeat the operation. While onCompleted()/onError() will be delivered as onCompleted()/onError() on your source Observable.
recommended reading regarding repeatWhen/retryWhen.
One thing to consider, as repeatWhen() will basically swallows all your onCompleted events (as you're repeating the operation there is no onCompleted(), your Observable will not stop from by itself!), then you should gather and update the adapter differently, I guess you can simply use toList() to gather all items to single onNext() (a replacement to your success() logic) and then on each onNext updates the list (what you're doing on onCompleted right now), to sum it up:
Observable.fromIterable(URLList)
.concatMap(url -> standartRequest(App.getInstance().getApi().getService().getData(currency.getUrl())))
.retry(Constants.RETRY_COUNT)
.timeout(Constants.TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)
.toList()
.repeatWhen(completed -> completed.delay(5, TimeUnit.SECONDS))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::valuesRetrieved, this::error);
EDIT:
Your timeout and retry logic are applied to the entire operation chain, so if all the network requests together take more than Constants.TIMEOUT_IN_SECONDS you will get timeout exception, you probably just want to retry and time out each individual request. like this:
Observable.fromIterable(URLList)
.concatMap(url -> standartRequest(App.getInstance()
.getApi().getService().getData(currency.getUrl())
.retry(Constants.RETRY_COUNT)
.timeout(Constants.TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)))
.toList()
.repeatWhen(completed -> completed.delay(5, TimeUnit.SECONDS))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::valuesRetrieved, this::error);
I am repeating my retrofit call every 2 second after it is completed
//Retrofit Builder
val retrofitBuilder = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl("http://worldtimeapi.org/")
.build()
val timeApi = retrofitBuilder.create(TimeApi::class.java)
val timeObservable = timeApi.getTime()
timeObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.repeatWhen { completed -> completed.delay(2, TimeUnit.SECONDS) }
.subscribe(object : Observer<Time> {
override fun onComplete() {
Log.e("MainActivity", "It is completed")
}
override fun onSubscribe(d: Disposable) {
Log.e("MainActivity", "you have successfully subscribed ")
}
override fun onNext(t: Time) {
progress.visibility = View.INVISIBLE
txtTime.text = t.unixtime
Log.e("MainActivity", "OnNext Called" + t.unixtime)
}
override fun onError(e: Throwable) {
Log.e("MainActivity", "ERROR")
}
})
}
See the Log Cat , onNext is called after every 2 second.
The repeatWhen can do the job, but in my sense, the interval could do the job as well, just like :
Observalbe.interval(5, TimeUnit.SECONDS)
.flatMap( /* Your Observabler.fromItere().concatMap().retry().timeout()*/)
.subscribe{ /* refresh RecyclerView* / }
In this way, you use flatMap to switch one stream (interval) to another stream (your business logic).
Related
I have a network call I make as part of a function that fetches the timer value for how long this data is going to be alive (basically the ttl of an object). I need to retrigger the same function as soon as the timer ends.
fun refresh() {
service.makeNetworkCall()
.subscribe { response ->
val ttl = response.ttl
retriggerAgainAfterTtlExpires(ttl)
}
I'm currently retriggering the function in the .doOnNext() call as shown below. But this doesn't chain the observable to the original one. It creates a whole new process and I want to avoid it.
fun retriggerAgainAfterTtlExpires(ttl:Long) {
Observable.interval(ttl, ttl, TimeUnit.SECONDS)
.doOnNext { refresh() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
How can I retrigger this function without having to call .doOnNext()
Save the TTL in a field and use deferred creation of the initial delay. At the end, simply use repeat.
private long ttl = 1000 // initial TTL
Observable.defer(() -> {
return Observable.just(ttl).delay(ttl, TimeUnit.MILLISECONDS);
})
.flatMap(v -> service.networkCall())
.doOnNext(response -> { ttl = response.ttl; })
.observeOn(mainThread())
// handle other aspects of response
.repeat()
.subscribe(/* ... */);
I am new into RxJava.
I am leaning by online resources and implement it.
I am trying to code very simple stuff but i am getting some issues.
var animalobservable: Observable<String> = Observable.just("Ant", "Bee", "Cat", "Dog", "Fox")
var animalObserver: Observer<String> = getAnimalObserver()
animalobservable
.subscribeOn(Schedulers.trampoline())
.debounce(3, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(animalObserver)
And i do some stuff in onNext method.
private fun getAnimalObserver(): Observer<String> {
return object : Observer<String> {
override fun onSubscribe(d: Disposable) {
Log.d("OnSubscibe", "onSubscribe")
}
override fun onNext(s: String) {
Log.d("OnNext", "Name: $s")
Toast.makeText(context, "Name : $s", Toast.LENGTH_SHORT).show()
}
override fun onError(e: Throwable) {
Log.e("OnError", "onError: " + e.message)
}
override fun onComplete() {
Log.d("OnComplete", "All items are emitted!")
}
}
}
My problem is when I print some code into Logcat it works fine. Data emitted one by one properly.
But when I toast it instead of Logcat it only emits last data "fox".
I want to know what's issue is going on for toast and logcat. I am assuming it happens because of threading but I am not getting why it happens.
Thanks in advance..!
You can see last toast because the toasts are immediately show sequentially and with same timing on each other, only we can see the last toast.
but if you want to add a delay between every emitting item, concat animalobservable with an observable with interval like this:
animalobservable
.subscribeOn(Schedulers.trampoline())
.concatMap { animal ->
Observable.interval(3, TimeUnit.SECONDS).take(1)
.map { animal }
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe(animalObserver)
Try changing from
Schedulers.trampoline()
to
Schedulers.io()
since trampoline is usually used in testing (unittest/UItest)
EDIT:
ok, when I reread this, just like #beigirad mentioned below, it's because of your debounce. For logging, it's running very fast so all values can be printed within 3 seconds. However, for Toast, it's much slower so the time expires and it emits your last result.
If you want to do intervals between onNext, you can write like this
animalobservable
.interval(3, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(animalObserver)
I currently have an EditText for the user to enter a search. I'm trying to use RxJava with debounce to only search every so often, instead of each character. However, I'm getting an InterruptedIOException while I'm testing, which kills the stream.
private val subject = BehaviorSubject.create<String>()
init {
configureAutoComplete()
}
private fun configureAutoComplete() {
subject.debounce(200, TimeUnit.MILLISECONDS)
.flatMap {
getSearchResults(query = it)
}
.subscribe({ result ->
handleResult(result)
}, { t: Throwable? ->
Logger.e(t, "Failed to search")
})
}
fun getSearchResults(query: String): Observable<List<MyObject>> {
val service = NetworkService.create() // get retrofit service
return service.search(query)
}
fun search(text: String) {
subject.onNext(text)
}
As you can see, I'm creating a BehaviorSubject, and within init I'm setting it up with debounce.
getSearchResult returns an Observable and does my network request.
But as I'm testing, if I type at a specific rate ( usually quick-ish, like typing another character while the request is ongoing ) it'll throw an Exception.
Failed to search : java.io.InterruptedIOException
at okhttp3.internal.http2.Http2Stream.waitForIo(Http2Stream.java:579)
at okhttp3.internal.http2.Http2Stream.takeResponseHeaders(Http2Stream.java:143)
at okhttp3.internal.http2.Http2Codec.readResponseHeaders(Http2Codec.java:125)
I was looking at this, https://stackoverflow.com/a/47276430/3106174, and it seems like I'm doing everything correctly.
After more testing, I realized that the network request was on the main thread.
You can test this by replacing your network call with Observerable.create{ ... } and throwing a Thread.sleep(1000) inside.
I was following this tutorial, https://proandroiddev.com/building-an-autocompleting-edittext-using-rxjava-f69c5c3f5a40, and one of the comments mention this issue.
"But I think one thing is misleading in your code snippet, and it’s
that subjects aren’t thread safe. And the thread that your code will
run on will be the thread that you emitting on (in this case the main
thread). "
To solve this issue, you need to force it to run on Schedulers.io(). Make sure it's after the debounce or it won't work.
private fun configureAutoComplete() {
subject.debounce(200, TimeUnit.MILLISECONDS)
.observeOn(Schedulers.io()) // add this here
.distinctUntilChanged()
.switchMap {
getSearchResults(query = it)
}
.subscribe({ result ->
handleResult(result)
}, { t: Throwable? ->
Logger.e(t, "Failed to search")
})
}
Retrofit first request with Single blocks UI thread. Below is relevant code, and more text:
RetrofitProvider
object RetrofitProvider {
private val TAG: String = RetrofitProvider::class.java.simpleName
val retrofit: Retrofit by lazy {
val httpClient = OkHttpClient.Builder()
.addInterceptor {
val request = it.request()
if (BuildConfig.DEBUG) {
Log.d(TAG, "${request.method()}: ${request.url()}")
}
it.proceed(request)
}
.build()
Retrofit.Builder()
.client(httpClient)
.baseUrl("http://192.168.0.10:3000")
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
.addConverterFactory(JacksonConverterFactory.create(jacksonObjectMapper()))
.build()
}
}
ProductApi
interface ProductApi {
#GET("/products")
fun getProducts(): Single<List<Product>>
}
MainViewModel
fun fetchProducts() {
productData.value = Resource.Loading()
productApi.getProducts() // <- This call is a problem (even when I comment out all code below)
.subscribeOn(Schedulers.io())
.subscribe(
{
productData.postValue(Resource.Success(it))
},
{
productData.postValue(Resource.Fail(it.message))
})
.addTo(disposableContainer)
}
MainFragment
...
button.setOnClickListener {
Toast.makeText(requireContext(), "click", Toast.LENGTH_SHORT).show()
mainViewModel.fetchProducts()
}
...
App flow is simple, clicking a button on MainFragment calls MainViewModel's fetchProducts() which uses retrofit to fetch some stuff.
productApi.getProducts() happens on UI thread and blocks it significantly(~half a second), even Toast is delayed, even though it should be shown immediately on button click, before getProducts() call.
productApi.getProducts() by itself, without subscribe doesn't send network request (I checked on server side), it just prepares Single.
Important note, delay DOES NOT happen on subsequent clicks to button. Just the first time, I guess creating Single<> is expensive operation.
So my question is, why is UI thread blocked on first request, and how do I fix it the way it isn't ugly/hacking.
Also Observable acts the same, but Completable works much faster, but I need the data, so can't use Completable.
I think your problem lies with the lazy initialisation of your Retrofit object.
It will be deferred to the last possible moment, so I guess the first time you click on the button, you create the expensive retrofit button (this is done on the main thread).
My suggestion is to remove the lazy initialisation and try running the app once again.
Returning Completable also blocks the UI thread but for less time than returning Single or Observable so it seems like it doesn't have any impact but it does.
Invoking the API call on a background thread will not block your UI as the converter creation will not happen on the UI thread.
Something like this does the trick.
Completable.complete()
.observeOn(Schedulers.io())
.subscribe {
productApi.getProducts()
.subscribe(
{
productData.postValue(Resource.Success(it))
},
{
productData.postValue(Resource.Fail(it.message))
}
)
.addTo(disposableContainer)
}
.addTo(disposableContainer)
Another thing you can do instead of using the converter is to make a wrapper class around the Retrofit API which will call it in a fitting observable on a background thread.
fun getProducts() = Single.create<List<Product>> { emitter ->
try {
val response = productApi.getProducts().execute()
if (!response.isSuccessful) {
throw HttpException(response)
}
emitter.onSuccess(response.body()!!)
} catch (e: Exception) {
emitter.onError(e)
}
}.observeOn(Schedulers.io())
When you invoke a RxJava action, for example, a retrofit request you can to tell it where to perform the action and where to get the result the default location is where you subscribe to it
in order to change it you need to add two lines
observeOn(Where you will receive the result)
subscribeOn(Where the action will be executed)
In your case, it should be something like this
productApi.getProducts() // <- This call is a problem (even when I comment out all code below)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io()) //or .subscribeOn(Schedulers.newThread())
.subscribe({Success},{Failure})
I have made a library that has a lot of utilities/extensions for Android development in kotlin.
One of the packages is there to make it simple to avoid this issue.
All you need to do is type:
yourObservable //or any other reactive type
.runSafeOnMain() //it will perform you action in another thread and it will return the result in main
.subscribe({}, {])
The method I want to test contains of two calls to the retrofit service:
internal fun poll(): Completable {
return presenceService.askForFrequency(true).toObservable()
.flatMap { it -> Observable.interval(it.frequency, TimeUnit.SECONDS, Schedulers.io()) }
.flatMapCompletable { _ -> presenceService.sendHeartbeat() }
.subscribeOn(Schedulers.io())
.retry()
}
The presenceService is injected in the class, so I provide the mocked one for the test:
val frequency = PublishSubject.create<Presence>()
val heartbeat = PublishSubject.create<Unit>()
val mockPresenceService = mock<PresenceService> {
on { askForFrequency(any()) } doReturn frequency
on { sendHeartbeat() } doReturn heartbeat
}
The test, that checks that askForFrequency method is called works correctly, but test that checks that the polling request is sent never works:
#Test
fun presenceService_sentHeartbeat() {
RxJavaPlugins.setIoSchedulerHandler { scheduler }
frequency.onNext(Presence(1)) //polls with 1s interval
heartbeat.onNext(Unit)
presenceMaintainer.onActivityResumed(any())
scheduler.advanceTimeBy(2, TimeUnit.SECONDS)
verify(mockPresenceService).askForFrequency(true) //works correctly
verify(mockPresenceService).sendHeartbeat() //never works
}
The logs from the unit test run are:
Wanted but not invoked:
presenceService.sendHeartbeat();
However, there was exactly 1 interaction with this mock:
presenceService.askForFrequency(true);
The question is: how to test that the second method (sendHeartbeat) is also called (possibly several times)?
Meanwhile I found out that the problem lies in the second flatmap, because the test for this method works correctly (verifies that method was called 60 times):
internal fun pollTest(): Observable<Presence> {
return Observable.interval(1, TimeUnit.SECONDS, Schedulers.io())
.subscribeOn(Schedulers.io())
.flatMap { it -> presenceService.askForFrequency(true).toObservable() }
}
#Test
fun presenceService_sentHeartbeat() {
frequency.onNext(Presence(1))
val result = arrayListOf<Unit>()
presenceMaintainer.pollTest().subscribe({ t -> result.add(Unit) })
Thread.sleep(60*1000)
println(result.size)
verify(mockPresenceService, Times(60)).askForFrequency(true)
}
But when I change the order of the calls to askForFrequency -> map to interval -> map each tick to poll call, test stops working and mock is called only once.
By default, Observable.interval() runs on the computation scheduler, and not the io scheduler. That means, that the 2 second wait will be run in real time, so your test will finish 2 seconds before the call to sendHeartBeat().