MBH
MBH

Reputation: 16609

Debounce rx observable based on condition - RxSwift

I use this in Kotlin to debounce based on condition:

// variables
private val subject_isUpdating = PublishSubject.create<Int>()
var lastClickedItem = -1


// inside onCreate
adapter_cartProducts.setOnItemClickedListener { position ->
     subject_isUpdating.onNext(position)
}


// subscribing 
        subject_isUpdating
            .debounce
            { position ->
                // here if lastClickedItem changed, no debounce
                if(position != lastClickedItem) {
                    lastClickedItem = position
                    Observable.empty()
                }
                // else if same item clicked -> debounce
                else Observable.timer(300, TimeUnit.MILLISECONDS) }
            .subscribe({ position ->
                updateOnWS(position, adapter_cartProducts.items[position])
            }, { error ->
                Timber.e(error) // printing the error
            })

This is the debounce selector function used from the RxJava:

/**
 * Returns an Observable that mirrors the source ObservableSource, except that it drops items emitted by the
 * source ObservableSource that are followed by another item within a computed debounce duration.
 * <p>
 * <img width="640" height="425" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/debounce.f.png" alt="">
 * <dl>
 *  <dt><b>Scheduler:</b></dt>
 *  <dd>This version of {@code debounce} does not operate by default on a particular {@link Scheduler}.</dd>
 * </dl>
 *
 * @param <U>
 *            the debounce value type (ignored)
 * @param debounceSelector
 *            function to retrieve a sequence that indicates the throttle duration for each item
 * @return an Observable that omits items emitted by the source ObservableSource that are followed by another item
 *         within a computed debounce duration
 * @see <a href="http://reactivex.io/documentation/operators/debounce.html">ReactiveX operators documentation: Debounce</a>
 */
public final <U> Observable<T> debounce(Function<? super T, ? extends ObservableSource<U>> debounceSelector) {
    ObjectHelper.requireNonNull(debounceSelector, "debounceSelector is null");
    return RxJavaPlugins.onAssembly(new ObservableDebounce<T, U>(this, debounceSelector));
}

The idea of this code, user will click on item of list, and the item will be updated on webservice when user stops clicking for 400ms or clicks another item

Is that possible to be done in RxSwift ?

Upvotes: 0

Views: 1442

Answers (2)

Sagar Pant
Sagar Pant

Reputation: 1

I feel the flatMapLatest can be used to solve the conditional debounce needed here.

subject
     .flatMapLatest { position in
           if position != lastClickedPosition {
               lastClickedItem = position
               return Observable.just(position)
           } else {
                return Observable.just(position).delay(.milliseconds(300), scheduler: scheduler)
           }
     }
// Subscribe 

I have this article on creating variable debounce using combine and RxSwift. Please check this out. https://medium.com/dev-genius/part-1-variable-time-debounce-in-swift-30a987511c6c

Upvotes: 0

Daniel T.
Daniel T.

Reputation: 33967

I can't say I especially like the code you presented because of its dependency on the external variable.

Here's an operator that does what you want:

extension ObservableType where E: Equatable {

    func throttleUnlessChanged(_ dueTime: TimeInterval, scheduler: SchedulerType) -> Observable<E> {
        return Observable.create { observer in
            let lock = NSRecursiveLock()
            var last: E?
            var lastTime: RxTime?
            return self.subscribe { event in
                lock.lock(); defer { lock.unlock() }
                switch event {
                case .next(let element):
                    let now = scheduler.now
                    let timeIntervalSinceLast = lastTime != nil ? now.timeIntervalSince(lastTime!) : dueTime
                    if element != last {
                        observer.onNext(element)
                        last = element
                        lastTime = now
                    }
                    else if timeIntervalSinceLast >= dueTime {
                        observer.onNext(element)
                        last = element
                        lastTime = now
                    }
                case .error(let error):
                    observer.onError(error)
                case .completed:
                    observer.onCompleted()
                }
            }
        }
    }
}

Here's a gist complete with tests: https://gist.github.com/dtartaglia/f5b041facfdcdd64630e0cb8cfc2cc5b

Upvotes: 2

Related Questions