Handling multiple button clicks with Reactive bindings on Android - android

I need to handle multiple button click within short period of time in a way such that I need to get number of click that the user clicked within 500ms to reduce the number of API calls I make to the backend.
val buttonStream = view.plusButton.clicks()
buttonStream
.buffer(buttonStream.debounce(500, TimeUnit.MILLISECONDS))
.map { it.size }
.subscribe({ clicks ->
Log.i(TAG, "Number of clicks: $clicks")
})
I have implemented the above code but it doesn't display anything when I click the button. When I remove .buffer(buttonStream.debounce(500, TimeUnit.MILLISECONDS)) and just add .buffer(500, TimeUnit.MILLISECONDS) Log starts printing every 500ms. Is there any way to get my work done?

Managed to get the expected out with the following code.
val buttonStream = view.plusButton.clicks()
buttonStream
.buffer(1000, TimeUnit.MILLISECONDS)
.map { it.size }
.filter { size -> size > 0 }
.subscribe({ clicks ->
Log.i(TAG, "Number of clicks from the QUICK_ADD: $clicks")
uploadWaterIntake(clicks)
})

Related

Get top 10 items RxJava

I am using RxJava to get list of Posts from JSONplaceholder api.
https://jsonplaceholder.typicode.com/posts
I want to take only the top 10 from the list and save in the data base.
I am aware I need to use take operator but cannot figure out how to use that with concatMap.
Here is what I have already.
private fun loadPosts(){
subscription = Observable.fromCallable { postDao.all }
.concatMap { dbPostList ->
postApi.getPosts().concatMap { apiPostList ->
//HERE i ONLY WANT TO TAKE 10 ITEMS AND SAVE IT (HOW CAN I USE TAKE OPERATOR HERE)
postDao.insertAll(*apiPostList.toTypedArray())
Observable.just(apiPostList)
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe { onRetrievePostListStart() }
.doOnTerminate { onRetrievePostListFinish() }
.subscribe(
{ result -> onRetrievePostListSuccess(result) },
{ onRetrievePostListError() }
)
}
Below code I tried and it does not work as expected.
postApi.getPosts()
.take(10) // DOES NOT WORK
.concatMap { apiPostList ->
postDao.insertAll(*apiPostList.toTypedArray())
Observable.just(apiPostList)
}
getPosts() returns a list. To use take(10) in your case you'd have to emit each element of the list individual. However, since you emit the entire list in one go, it is as if take(10) is trying to take 10 lists of posts rather than 10 posts.
I can think of 2 ways to fix this. You can convert the list to and observable like:
postApi.getPosts()
.flatMap { Observable.fromIterable(it) }
.take(10)
.toList()
Emit each item of the list, take 10 of them and collect the results in a list ready for your concatMap.
Another option is to manually slice the list:
postApi.getPosts()
.map { it.slice(0 until 10) }
Not so rx-ish but still should work.
Careful because both approaches assume there are at least 10 items in the list.

How can I achieve this requirement using Rx Java

I have a State(Enum) that contains (Good, Non-Critical, Critical) values
So requirement is :
should trigger when state goes in non-critical state.
should trigger when state goes in critical state.
should trigger when state stays in critical state for 15 seconds.
Input :
publishSubject.onNext("Good")
publishSubject.onNext("Critcal")
publishSubject.onNext("Critcal")
publishSubject.onNext("NonCritical")
publishSubject.onNext("Critacal")
publishSubject.onNext("Critical")
publishSubject.onNext("Good")
and so on...
See Code Structure for Reference:
var publishSubject = PublishSubject.create<State>()
publishSubject.onNext(stateObject)
publishSubject
/* Business Logic Required Here ?? */
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
AppLogger.printLog("Trigger Success --> ")
}
Please help,
Thanks in Advance,
You can use distinctUntilChanged() to suppress events that don't change the state. Filter out the normal events using filter().
Use the switchMap() operator to create a new subscription when the state changes. When the state is "critical", use the interval() operator to wait out the 15 seconds. If the state changes in that 15 seconds, switchMap() will unsubscribe and re-subscribe to a new observable.
publishSubject
.distinctUntilChanged()
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.filter( state -> state != State.Normal )
.switchMap( state -> {
if (state == State.Critical) {
return Observable.interval(0, 15, TimeUnit.SECONDS) // Note 1
.map(v -> State.Critical); // Note 2
}
return Observable.just( State.Noncritical );
})
.subscribe( ... );
interval() is given an initial value of 0, causing it to emit a value immediately. After 15 seconds, the next value will be emitted, and so on.
The map() operator turns the Long emitted by interval() into
The first two parts of your requirements should be combined into one. You're asking for the chain to be triggered on NonCritical and Critical events, ergo the chain should not be triggered for Good event. Likewise, you only need to trigger an event if the state is different from a previous event. For this two .filter events should suffice:
var lastKnownState: State = null
publishSubject
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.filter(this::checkStateDiffers) // Check we have a new state
.filter { state -> state != State.Good } // Check event is good
.subscribe {
AppLogger.printLog("Trigger Success --> ")
}
...
private fun checkStateDiffers(val state: State): Boolean {
val isDifferent = state != lastKnownState
if (isDifferent) lastKnownState = state // Update known state if changed
return isDifferent
}
The timeout requirement is a bit trickier. RxJava's timeout() operator gives the option of emitting an error when nothing new has been received for a period of time. However I am assuming that you want to keep listening for events even after you receive a timeout. Likewise, if we just send another Critical event it'll be dropped by the first filter. So in this case I'd recommend a second disposable that just has the job of listening for this timeout.
Disposable timeoutDisp = publishSubject
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.timeout(15, TimeUnit.SECONDS)
.onErrorResumeNext(State.Timeout)
.filter { state -> state == State.Timeout }
.filter { state -> lastKnownState == State.Critical }
.subscribe {
AppLogger.printLog("Timeout Success --> ")
}
Also adjust the checkStateDiffers() to not save this Timeout state in the first chain.
private fun checkStateDiffers(val state: State): Boolean {
if (state == State.Timeout) return true
var isDifferent = state != lastKnownState
if (isDifferent) lastKnownState = state // Update known state if changed
return isDifferent
}

RxJava on error perform onErrorResumeNext() but also take some action

While iterating and fetching web responses the chain stops when it encounters an error.
I used .onErrorResumeNext(Observable.empty()) to keep the iteration going but want to do some error handling too. How can this be done?
.getRepos()
.flatMap { itemList ->
//fetches all repos
Observable.fromIterable(itemList)
}
.concatMapEager { githubItem ->
//fetches commits of each repos
networkModule.getCommits().map { commitItem ->
dataBaseModule.insertRepo(commitItem)
}.onErrorResumeNext(Observable.empty())
//here I want to take some action instead of just passing an empty observable
}
You can use the doOnError() operator just before the onErrorResumeNext() to perform an action.
...
.doOnError( error -> {} )
.onErrorResumeNext( Observable.empty() )
...

RxJava Observable to generate repeated events when button is down

I'm trying to write an observable that would generate repeated events while the user holds down a view. My code below works well, but only the first time (e.g. if the user presses the button again, nothing happens). Can you please advise what am I doing wrong and what is best practice for this?
val touches = RxView.touches(previousButton)
touches
.filter({ event -> event.action == MotionEvent.ACTION_DOWN })
.flatMap({
Observable.interval(500, 50, TimeUnit.MILLISECONDS)
.takeUntil(touches.filter({event -> event.action == MotionEvent.ACTION_UP}))
}).subscribe({ println("down") })
The problem is that the RxView.touches observable cannot exist for more than 1 source. This means when the subscription inside of the flatMap happens it breaks the original subscription used to trigger the flatMap, making it never occur again.
There are two possible ways around this:
Use .publish(...) to share the source of events instead of using touches.
Map the events into a Boolean on/off observable, then switchMap the appropriate actions based on the current value of the observable.
1.
touches.publish { src ->
src.filter(...)
.flatMap {
Observable.interval(...)
.takeUntil(src.filter(...))
}
}
2.
touches.filter {
it.action == MotionEvent.ACTION_DOWN
or it.action == MotionEvent.ACTION_UP
}
.map { it.action == MotionEvent.ACTION_DOWN }
.distinctUntilChanged() // Avoid repeating events
.switchMap { state ->
if (state) {
Observable.interval(...)
} else {
Observable.never()
}
}

RxJava 2 Observable with flatMapCompletable doesn't complete

I have an observable that emits items and upload them to server.
Here is the code:
repository
.getItems()
.doOnComplete(() -> Log.d(TAG, "No items left."))
.flatMapCompletable(item ->
repository
.uploadItems(item)
.onErrorComplete()
.andThen(
deleteTemporaryItem()
.onErrorComplete()
)
);
getItems method emits items one by one and then completes, uploadItems method upload them to server. The issue is when there is no items all chain onComplete event just working fine and all my subscribers get this event and proceed it BUT when there were some items and all of them were uploaded onComplete events doesn't go further than .doOnComplete(() -> Log.d(TAG, "No items left.")) method and all subscribers doesn't get this event. I added onErrorComplete to be sure that all methods after uploadItems completes and I also see in logs that all of them were completed but onComplete event from repository.getItems() doesn't go to all subscribers.
Could anyone please help to figure out what could be the reason for this behavior?
Thanks in advance!
Please have a look at this example:
I pass the item through each step, so the subscribe will be notified on each item that has been processed. The processing pipeline involves uploading and deleting the file.
Please try to change the implementation and post a log of the output.
#Test
void name() throws Exception {
Flowable<Integer> completed_work = Flowable.just(1, 2, 3)
.map(integer -> integer * 1000)
.flatMapSingle(integer ->
Completable.fromAction(() -> {
Thread.sleep(integer);
// do upload stuff here
})
.doOnComplete(() -> System.out.println("Uploaded file ...."))
//.timeout(10, TimeUnit.SECONDS)
.retry(3)
.andThen(
Completable.fromAction(() -> {
// do delete stuff...
})
.retry(2)
//.timeout(10, TimeUnit.SECONDS)
.doOnComplete(() -> System.out.println("Deleted file ..."))
)
.toSingle(() -> integer)
)
.doOnComplete(() -> System.out.println("Completed work"));
completed_work.test()
.await()
.assertResult(1000, 2000, 3000);
}

Categories

Resources