0

I am trying to create a simple timer with RxSwift and I inspired from this answer (). It works fine when the application is in foreground. The problem is that if the application state goes to the background, it stops and then starts to count from where it stays if the app state is foreground again.

https://stackoverflow.com/a/41198811/3950721

To quick look; Simple Timer with RxSwift

Ömer Ulusal
  • 139
  • 1
  • 4

2 Answers2

2

In addition to Daniel's answer, if you have a similar problem with mine(timer with expiration like one time password screen), here is my solution;

Thanks to expire date, as long as the timer runs, it emits the remaining seconds to the expire date. Since expire date is constant, the remaining seconds are not affected by the application state.

let expireDate = Date().addingTimeInterval(TimeInterval(seconds))
timerBag = DisposeBag() // to release timer.
Observable<Int>
    .interval(.seconds(1), scheduler: MainScheduler.instance)
    .map { _ in expireDate }
    .compactMap( expireDate -> Int? in
        let calendar = Calendar.current
        let components = calendar.dateComponents([.second], from: Date(), to: expireDate)
        return components.second
    }
    .subscribe(onNext: { [weak self] remainingSecondsToExpire in
        print(remainingSecondsToExpire)
    }).disposed(by: timerBag!)
Ömer Ulusal
  • 139
  • 1
  • 4
0

What you are experiencing is standard and expected behavior. When an app goes to the background, all timers and other background tasks (whether in Rx or not) stop.

In order for anybody to help you, you will need to explain what it is you are trying to do so we can come up with an alternative way to do it.

For example, in one of my apps, the user is supposed to be logged out after 5 minutes of inactivity, so I have this to make sure it happens:

let idleTime = 5 * 60
let foregroundTimerTripped = Observable.merge(
    application.rx.methodInvoked(#selector(UIApplication.sendEvent(_:))).map(to: ()),
    rx.methodInvoked(#selector(UIApplicationDelegate.applicationWillEnterForeground(_:))).map(to: ())
)
    .debounce(.seconds(idleTime), scheduler: MainScheduler.instance)

let backgroundTime = rx.methodInvoked(#selector(UIApplicationDelegate.applicationDidEnterBackground(_:)))
    .map(to: ())
    .flatMap { Observable.just(Date()) }
let foregroundTime = rx.methodInvoked(#selector(UIApplicationDelegate.applicationWillEnterForeground(_:)))
    .map(to: ())
    .flatMap { Observable.just(Date()) }
let backgroundTimerTripped = foregroundTime
    .withLatestFrom(backgroundTime) { $0.timeIntervalSince($1) }
    .filter { $0 > TimeInterval(idleTime) }
    .withLatestFrom(bearer)

let timeToLogout = Observable.merge(foregroundTimerTripped, backgroundTimerTripped)
Daniel T.
  • 32,821
  • 6
  • 50
  • 72
  • Daniel, thanks. I will use it for a page takes input for one time password. Timer shows the remaining seconds before expiration of OTP. I solved my problem by keeping the expire date. New timer calculates remaining time to expire date each second. – Ömer Ulusal Jun 04 '20 at 19:40