I'm learning RxSwift, what I would like achieve is to get mechanism which prints me text from UITextFied, but after given interval.
How it works now: when I type first character this character is immediately printed out (not after delay how I expected) and if I keep on typing long sentence, text is printed after each two second (as interval is set in throttle), but I would like to have only latest text value.
My code:
inputField.rx.text.orEmpty
.throttle(2, latest: true, scheduler: MainScheduler.instance)
.subscribe(onNext: { text in
print("\(text)")
}, onDisposed: nil)
.addDisposableTo(disposeBag)
Im looking for your help Rx fellows :) Thanks
Use debounce instead of throttle.
From the documentation for debounce:
Ignores elements from an observable sequence which are followed by
another element within a specified relative time duration, using the
specified scheduler to run throttling timers.
From the documentation for throttle.
Returns an Observable that emits the first and the latest item emitted
by the source Observable during sequential time windows of a specified
duration.
Related
I'm using a rxdart ZipStream within my app to combine two streams of incoming bluetooth data. Those streams are used along with "bufferCount" to collect 500 elements each before emitting. Everything works fine so far, but if the stream subscription gets cancelled at some point, there might be a number of elements in those buffers that are omitted after that. I could wait for a "buffer cycle" to complete before cancelling the stream subscription, but as this might take some time depending on the configured sample rate, I wonder if there is a solution to get those buffers as they are even if the number of elements might be less than 500.
Here is some simplified code for explanation:
subscription = ZipStream.zip2(
streamA.bufferCount(500),
streamB.bufferCount(500),
(streamABuffer, streamBBuffer) {
return ...;
},
).listen((data) {
...
});
Thanks in advance!
So for anyone wondering: As bufferCount is implemented with BufferCountStreamTransformer which extends BackpressureStreamTransformer, there is a dispatchOnClose property that defaults to true. That means if the underlying stream whose emitted elements are buffered is closed, then the remaining elements in that buffer are emitted finally. This also applies to the example above. My fault was to close the stream and to cancel the stream subscription instantly. With awaiting the stream's closing and cancelling the stream subscription afterwards, everything works as expected.
Im new to RxSwift and what Im trying is perform the following,
User type is search UiTextField
App must wait 1 sec after user finish typing to make sure not to hit the Api with each letter write or delete button pressed.
App hit my Api for search
I tried the below code as example
class DestinationSearch: UIViewController {
let searchTF = UITextField()
var disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
setUpUI()
searchTF.rx.controlEvent(.editingChanged)
.throttle(.milliseconds(1000), scheduler: MainScheduler.instance)
.withLatestFrom(searchTF.rx.text)
.subscribe(onNext:{ query in
print(self.hitSomeApi(query: query ?? "--"))
}).disposed(by: disposeBag)
}
func hitSomeApi(query: String) -> String {
return query + " Response from API"
}
When I run the app and start typing I get the the Response from API message with each letter or backspace button pressed! why the throttle delay is not working? Im I doing something wrong here?
Any help will be much appreciated
Based on your description of the problem, it seems like the debounce operator is more suitable than throttle. debounce only emits an element if a certain time, in which nothing has been emitted, has passed, whereas throttle ensures that elements are emitted at least a certain time interval apart.
I can confirm that your code using throttle is working as I'd expect - if I type very quickly, the "Response from API" messages appear roughly every 1 second. If I type very slowly, slower than 1 keystroke per second, then the messages come whenever I press a key. In other words, whenever there is an editing changed event, throttle checks to see if there was a previous one less than 1 second ago. If there is, ignore this new one.
If you use debounce however (the same code, just replace throttle with debounce), then after each keystroke, it would wait for 1 second to see if the text field is going to change again. It would only emit the editing changed event if it has waited and there is no more editing changed events. So if you keep typing at a pace higher than 1 keystroke per second, no elements will ever be emitted by the observable. This seems to be what you want.
You can compare these two operators on RxMarbles (they are called slightly different names there):
debounceTime
throttleTime
I was trying to assign timestamp and watermarks to a stream by implementing the AssignerWithPeriodicWatermarks, inside the function, it implements:
override def getCurrentWatermark: Watermark = {
// this guarantees that the watermark never goes backwards.
val potentialWM = currentMaxTimestamp - maxOutOfOrderness
if (potentialWM >= lastEmittedWatermark) lastEmittedWatermark = potentialWM
new Watermark(lastEmittedWatermark)
}
override def extractTimestamp(element: T, previousElementTimestamp: Long): Long = {
val timestamp = element.streamTime // something exists in the stream
if (timestamp > currentMaxTimestamp) currentMaxTimestamp = timestamp
timestamp
}
However, I still got watermarks of the default value -9223372036854775808, and when I tried to add printing inside both functions, I found only println inside extractTimestamp was printed, which is saying function of getCurrentWatermark was never called.
The implementations seem to be right, because the same code was able to run on another script(some code not written by me).
PS: It's not the first time that I encountered negative watermark, what I found is that after a certain period of time, the watermark will go positive, however I am still quite confused what happened at the beginning.
The issue is that You are using AssignerWithPeriodicWatermark which does not generate watermarks per event but in intervals. Whenever You are using AssingerWithPeriodicWatermark You should set call the setTheAutowatermarkInterval on the execution environment. The value that You provide there will be the interval that the getCurrentWatermark will be called with.
If You haven't set it then the method will never be called, thus You will never have a watermark changed.
For testing and learning, You can consider using AssignerWithPunctuatedWatermark as this will simply emit watermark for each event.
EDIT:
As it was mentioned below this answer, the default value for autowatermarkInterval is actually 200 ms. Also, using the AssignerWithPunctuatedWatermark doesn't mean You need to emit the Watermark for each event, but the method for emitting them will be called for each event. If You don't want to emit the Watermark then the method should simply return null.
I have an observable object and I want to emit a default element after some time has passed (a timeout) while keeping the stream still open to emit new values in the future. How can I do this?
I attempted to do this by doing a merge of the original stream with another stream that debounces the original one while mapping the debounced value to the default that I want.
Pseudocode:
defaultDebounced = originalStream.debounce(time).map({x -> myDefaultValue})
myStream = rx.merge(originalStream, defaultDebounced)
though I don't know if I would run into some border cases like the following in which the original stream emits an item just as the timeout triggers and by chance, the default value gets emitted afterwards.
original: ----A----B----------------------C------------
debounced: -----------------------<timeout>X------------
merged: --------------------------------CX-----------
Also, there's the downside that the first observable has to emit at least one item in order for debounce to emit the default value.
Note: I would like to know the proper rx way to do it, regardless of the implementation, but just in case I'm working on RxSwift.
What I finally did was:
originalStream.flatMapLatest({x ->
return Observable.timer(30, scheduler: MainScheduler.instance)
.map{_ -> defaultValue}
.startWith(x)
})
I'm trying to use delay and amb to execute a sequence of the same task separated by time.
All I want is for a download attempt to execute some time in the future only if the same task failed before in the past. Here's how I have things set up, but unlike what I'd expect, all three downloads seem to execute without delay.
Observable.amb([
Observable.catch(redditPageStream, Observable.empty()).delay(0 * 1000),
Observable.catch(redditPageStream, Observable.empty()).delay(30 * 1000),
Observable.catch(redditPageStream, Observable.empty()).delay(90 * 1000),
# Observable.throw(new Error('Failed to retrieve reddit page content')).delay(10000)
# Observable.create(
# (observer) ->
# throw new Error('Failed to retrieve reddit page content')
# )
]).defaultIfEmpty(Observable.throw(new Error('Failed to retrieve reddit page content')))
full code can be found here. src
I was hoping that the first successful observable would cancel out the ones still in delay.
Thanks for any help.
delay doesn't actually stop the execution of what ever you are doing it just delays when the events are propagated. If you want to delay execution you would need to do something like:
redditPageStream.delaySubscription(1000)
Since your source is producing immediately the above will delay the actual subscription to the underlying stream to effectively delay when it begins producing.
I would suggest though that you use one of the retry operators to handle your retry logic though rather than rolling your own through the amb operator.
redditPageStream.delaySubscription(1000).retry(3);
will give you a constant retry delay however if you want to implement the linear backoff approach you can use the retryWhen() operator instead which will let you apply whatever logic you want to the backoff.
redditPageStream.retryWhen(errors => {
return errors
//Only take 3 errors
.take(3)
//Use timer to implement a linear back off and flatten it
.flatMap((e, i) => Rx.Observable.timer(i * 30 * 1000));
});
Essentially retryWhen will create an Observable of errors, each event that makes it through is treated as a retry attempt. If you error or complete the stream then it will stop retrying.