react-native-background-timer icon indicating copy to clipboard operation
react-native-background-timer copied to clipboard

Timer is slower than an actual timer

Open MrShakes opened this issue 5 years ago • 14 comments

The timer is slower(noticeable within 1 minute) than an actual timer or Javascript's default setInterval, I tried using runBackgroundTimer and setInterval(for Android), the latter being a bit faster however it would still lag behind an actual timer or Javascript's setInterval. Used on an actual device outside dev.

Code(using with Redux):

BackgroundTimer.runBackgroundTimer(() => {
    // code that will be called every 1 second
    tickTock(dispatch);
}, 1000);

"react-native-background-timer": "2.4.1" "react-native": "0.61.5"

MrShakes avatar Oct 04 '20 09:10 MrShakes

Same isssue here !

tisch7 avatar Oct 05 '20 14:10 tisch7

@MrShakes Did you find a solution?

Skr1pt1k avatar Nov 13 '20 12:11 Skr1pt1k

Same issue

VTSingle avatar Nov 16 '20 14:11 VTSingle

Same issue, did anyone find a way to fix that ? This behaviour was mentioned in issue #28 as well.

hadri3n avatar Nov 27 '20 13:11 hadri3n

Maybe this can help: https://github.com/ocetnik/react-native-background-timer/issues/258#issuecomment-749721388

jnjacobson avatar Dec 22 '20 19:12 jnjacobson

same issue .10 milliseconds delay every called

serapme avatar Jan 11 '21 02:01 serapme

same here about 5s per minute offset :( anyone found a workaround yet?

taschik avatar Feb 05 '21 19:02 taschik

I attempted to use this for a stopwatch implementation and also experienced this issue. I ended up using a JS implementation instead. In reflection the README of this library does not claim to guarantee accuracy.

david-gettins avatar Aug 04 '21 17:08 david-gettins

@david-gettins hi! Can you describe in two words how you implemented it with JS? I tried to do that using Headless JS, but it seems like it involves some Java coding, and i'm not good at Java:)

maxim-green avatar Apr 08 '22 10:04 maxim-green

@david-gettins hi! Can you describe in two words how you implemented it with JS? I tried to do that using Headless JS, but it seems like it involves some Java coding, and i'm not good at Java:)

Although a JS implementation worked on iOS, on Android it stopped when the app was backgrounded.

I ended up creating my own React Native Native Module, in Kotlin for Android and Swift for iOS. I employed techniques described in the Android developer docs and Apple developer docs to get exact timing and now the solution works with exact second timing and while apps are backgrounded.

Unfortunately this was for an internal project for the company I work full time for. I'll have to ask for permission to publish the code publicly before I share any implementation details.

david-gettins avatar Apr 08 '22 11:04 david-gettins

@mgeorgievsky To answer your question more directly, it was using setTimeout and correcting the next interval period by diffing the previous fire time with the expected time and adjusting the next millisecond value. If you don't already know setTimeout and setInterval are not exact and may fire before or after the specified period.

david-gettins avatar Apr 08 '22 12:04 david-gettins

@mgeorgievsky thanks for your answer! As a result i just did like that: https://github.com/ocetnik/react-native-background-timer/issues/258#issuecomment-749721388

maxim-green avatar Apr 12 '22 09:04 maxim-green

Here a more recent implementation (hook) for a stopwatch. I think that can fix this delay issue (not tested on Android, though):

import {useState, useRef, useEffect} from 'react';
import BackgroundTimer from 'react-native-background-timer';
import {mmkvStorage} from 'App';

const padStart = (num: number) => {
  return num.toString().padStart(2, '0');
};

const formatMs = (milliseconds: number) => {
  let seconds = ~~(milliseconds / 1000);
  let minutes = ~~(seconds / 60);
  let hours = ~~(minutes / 60);

  minutes = minutes % 60;
  seconds = seconds % 60;
  const ms = ~~((milliseconds % 1000) / 10);

  let str = `${padStart(minutes)}:${padStart(seconds)}.${padStart(ms)}`;

  if (hours > 0) {
    str = `${padStart(hours)}:${str}`;
  }

  return str;
};

const enum MMKV_KEYS {
  timeWhenLastStopped = 'timeWhenLastStopped',
  isRunning = 'isRunning',
  startTime = 'startTime',
}

export const useStopWatch = () => {
  const [time, setTime] = useState(0);
  const [isRunning, setIsRunning] = useState(false);
  const [startTime, setStartTime] = useState<number>(0);
  const [timeWhenLastStopped, setTimeWhenLastStopped] = useState<number>(0);
  const dataLoaded = useRef(false);

  useEffect(() => {
    // loading persisted data
    const timeWhenLastStopped = mmkvStorage.getNumber(
      MMKV_KEYS.timeWhenLastStopped,
    );
    const isRunning = mmkvStorage.getBoolean(MMKV_KEYS.isRunning);
    const startTime = mmkvStorage.getNumber(MMKV_KEYS.startTime);

    setTimeWhenLastStopped(timeWhenLastStopped ? timeWhenLastStopped : 0);
    setIsRunning(!!isRunning);
    setStartTime(startTime ? startTime : 0);
    dataLoaded.current = true;
  }, []);

  useEffect(() => {
    const persist = () => {
      try {
        mmkvStorage.set(MMKV_KEYS.timeWhenLastStopped, timeWhenLastStopped);
        mmkvStorage.set(MMKV_KEYS.isRunning, isRunning);
        mmkvStorage.set(MMKV_KEYS.startTime, startTime);
      } catch (e) {
        console.log('Error persisting data', e);
      }
    };

    if (dataLoaded.current) persist();
  }, [timeWhenLastStopped, isRunning, startTime, dataLoaded.current]);

  useEffect(() => {
    if (startTime > 0) {
      BackgroundTimer.runBackgroundTimer(() => {
        setTime(() => Date.now() - startTime + timeWhenLastStopped);
      }, 1);
    } else {
      BackgroundTimer.stopBackgroundTimer();
    }
  }, [startTime]);

  const start = () => {
    setIsRunning(true);
    setStartTime(Date.now());
  };

  const pause = () => {
    setIsRunning(false);
    setStartTime(0);
    setTimeWhenLastStopped(time);
  };

  const reset = () => {
    setIsRunning(false);
    setStartTime(0);
    setTimeWhenLastStopped(0);
    setTime(0);
  };

  return {
    start,
    pause,
    reset,
    isRunning,
    rawTime: time,
    time: formatMs(time),
    hasStarted: time > 0,
  };
};

hadnet avatar Mar 01 '23 18:03 hadnet

For anyone still experiencing this issue (despite the solution above from @hadnet), I solved it with the following:

For iOS it works with JS own setInterval so no additional package needed here. For Android I found the package: https://github.com/juanamd/react-native-background-timer-android that states to be an improvement of this package. I gave it a go and it works perfect. However, in dev mode on real device when returning from background the UI could lag from time to time. But when testing on a production apk, it works smoothly. No delay in time or any other problem. Hopefully this helps anyone still struggling with this problem.

thatgriffith avatar May 16 '23 09:05 thatgriffith