iosswiftrx-swiftrxalamofire

Periodically call an API with RxSwift


I'm trying to periodically (every 10 seconds) call an API that returns a Json object of model :

struct MyModel { 
   var messagesCount: Int?
   var likesCount: Int?
}

And update the UI if messageCount or likesCount value changes. I tried the Timer solution but i find it a little bit messy and i want a cleaner solution with RxSwift and RxAlamofire.

Any help is highly appreciated as i'm new to Rx.


Solution

  • There's quite a lot of operators required for this, and I would recommend to look them up on the ReactiveX Operator page, which I check every time I forget something.

    First off, ensure MyModel conforms to Decodable so it can be constructed from a JSON response (see Codable).

    let willEnterForegroundNotification = NotificationCenter.default.rx.notification(.UIApplicationWillEnterForeground)
    let didEnterBackgroundNotification = NotificationCenter.default.rx.notification(.UIApplicationDidEnterBackground)
    
    let myModelObservable = BehaviorRelay<MyModel?>(value: nil)
    
    willEnterForegroundNotification
        // discard the notification object
        .map { _ in () }
        // emit an initial element to trigger the timer immediately upon subscription
        .startWith(())
        .flatMap { _ in 
            // create an interval timer which stops emitting when the app goes to the background
            return Observable<Int>.interval(10, scheduler: MainScheduler.instance)
                .takeUntil(didEnterBackgroundNotification)
        }
        .flatMapLatest { _ in 
            return RxAlamofire.requestData(.get, yourUrl)
                // get Data object from emitted tuple
                .map { $0.1 } 
                // ignore any network errors, otherwise the entire subscription is disposed
                .catchError { _ in .empty() } 
        } 
        // leverage Codable to turn Data into MyModel
        .map { try? JSONDecoder().decode(MyModel.self, from: $0) } }
        // operator from RxOptional to turn MyModel? into MyModel
        .filterNil() 
        .bind(to: myModelObservable)
        .disposed(by: disposeBag)
    

    Then, you can just continue the data stream into your UI elements.

    myModelObservable
        .map { $0.messagesCount }
        .map { "\($0) messages" }
        .bind(to: yourLabel.rx.text }
        .disposed(by: disposeBag)
    

    I didn't run this code, so there might be some typos/missing conversions in here, but this should point you in the right direction. Feel free to ask for clarification. If are really new to Rx, I recommend going through the Getting Started guide. It's great! Rx is very powerful, but it took me a while to grasp.

    As @daniel-t pointed out, the background/foreground bookkeeping is not necessary when using Observable<Int>.interval.