makle
makle

Reputation: 1267

Timer stops running when screen is locked (react-native)

My timer stops when I'm locking my screen. The function looks like this so far:

function startTimer(dispatch, getState, duration, tickDuration = 1000) {
  return new Promise((resolve, reject) => {
    // XXX: Gross local state to keep track of remaining seconds
    let remaining = duration;
    let handle = setInterval(function() {
      const timerState = getState().countdown;
      // Clear either if the timer stopped or if the handle is a different
      // handle (ex. new timer started before this one completed)
      if (!timerState.inProgress || timerState.timerHandle !== handle) {
        clearInterval(handle);
        resolve(false);
      } else {
        remaining--;
        if (remaining === 0) {
          clearInterval(handle);
          resolve(true);
        }

        dispatch({
          type: TIMER_COUNTDOWN_TICK,
        });
      }
    }, tickDuration);

    dispatch({
      type: TIMER_COUNTDOWN_START,
      duration: duration,
      timerHandle: handle,
    });
  });
}

Any idea how I can change this, so that it also runs when the user locks the screen and then would come back?

Thanks!

Upvotes: 1

Views: 3130

Answers (3)

makle
makle

Reputation: 1267

I solved it with comparing 2 date objects and then getting the seconds from it to correctly update my UI. The suggested react-native-background-timer module didn't work for me. My startTimer function looks now like that:

function startTimer(dispatch, getState, duration, tickDuration = 1000) {
  return new Promise((resolve, reject) => {
    // XXX: Gross local state to keep track of remaining seconds
    let remaining = duration;
    var endTime = new Date();
    endTime.setSeconds(endTime.getSeconds() + remaining);
    let handle = setInterval(function() {
      const timerState = getState().countdown;
      // Clear either if the timer stopped or if the handle is a different
      // handle (ex. new timer started before this one completed)
      if (!timerState.inProgress || timerState.timerHandle !== handle) {
        clearInterval(handle);
        resolve(false);
      } else {
        var compareTime = new Date();
        if (compareTime > endTime) {
          clearInterval(handle);
          resolve(true);
        }

        dispatch({
          type: TIMER_COUNTDOWN_TICK,
          endTime: endTime,
        });
      }
    }, tickDuration);

    dispatch({
      type: TIMER_COUNTDOWN_START,
      duration: duration,
      timerHandle: handle,
    });
  });
}

And then I adjusted the dispatch function as well:

function countdown(state = {
  remaining: 0,
  inProgress: false,
  timerHandle: -1,
}, action) {
  switch (action.type) {
    case TIMER_COUNTDOWN_START:
      return Object.assign({}, state, {
        remaining: action.duration,
        inProgress: true,
        timerHandle: action.timerHandle,
      });
    case TIMER_COUNTDOWN_TICK:
      var endTime = action.endTime;
      var compareTime = new Date();
      var secondsDifference = Math.abs(endTime - compareTime) / 1000;

      return Object.assign({}, state, {
        remaining: secondsDifference,
        inProgress: true,
      });
    case REHYDRATE:
      const incoming = action.payload.countdown;
      if (incoming) {
        return {
          ...state,
          ...incoming,
          remaining: processCountdownRehydration(incoming.remaining),
          inProgress: false,
          timerHandle: -1,
        };
      }
      return state;
    case USER_LOG_OUT:
    case BACK_BUTTON_PRESSED:
      return { ...state, remaining: 0, inProgress: false, timerHandle: -1 };
    default:
      return state;
  }
}

Upvotes: 1

Hermenpreet Singh
Hermenpreet Singh

Reputation: 474

You have to run the timer at background of the app Use this module react-native-background-timer your process is running on background.

import BackgroundTimer from 'react-native-background-timer';

Upvotes: 2

Jonas Wilms
Jonas Wilms

Reputation: 138267

The problem is that intervals are frozen when you leave the tab (e.g. through locking) so you cant guarantee that setInterval always takes a second to run. You may use the clocktime to check instead:

  const delay = t => new Promise(res => setTimeout(res, t));

  async function timer(duration, tickDuration = 1000){
   const end = +new Date() + duration;
   while(+new Date() < end) await delay(tickDuration);
  }

So one can do

 timer(20000).then(console.log)

Upvotes: 1

Related Questions