Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for AlarmManager in Timing to allow proper handling of long timers #12981

Closed
astreet opened this issue Mar 16, 2017 · 173 comments
Closed
Labels

Comments

@astreet
Copy link
Contributor

astreet commented Mar 16, 2017

Setting timers for multiple minutes isn't handled properly in React Native on Android: it keeps the Timing module awake instead of relying on the system waking us up when the timer should go off.

We should explore setting a cut off at which we delegate to AlarmManager and Handler.postDelayed instead of handling the timers using framecallbacks.

@ptomasroos
Copy link
Contributor

Great improvement!

@chirag04
Copy link
Contributor

this would help with socket.io which keeps a timer of 85000ms by default. On RN master the threshold is 60000ms.

@skv-headless
Copy link
Contributor

And Firebase which is also using long timers.

@imyagnesh
Copy link

I am getting following warning while using firebase library

Setting a timer for a long period of time, i.e. multiple minutes, is a performance and correctness issue on Android as it keeps the timer module awake, and timers can only be called when the app is in the foreground. See https://github.com/facebook/react-native/issues/12981 for more info. (Saw setTimeout with duration 111862ms)

How to get rid of this warning...

@oky1
Copy link

oky1 commented May 15, 2017

I am getting following warning while using firebase library too. Somebody know how to solve this problem?

@VickyKoblinski
Copy link

I am also getting this warning with firebase.

"firebase": "^3.9.0",
"react-native": "0.44.0"

@imamatory
Copy link

Same issue (85000ms) but without firebase. My list of packages:

  "dependencies": {
    "apisauce": "0.11.0",
    "format-json": "1.0.3",
    "lodash": "4.17.4",
    "markdown-it": "^8.3.1",
    "native-base": "^2.1.3",
    "normalizr": "^3.2.2",
    "prop-types": "^15.5.10",
    "querystringify": "1.0.0",
    "ramda": "0.23.0",
    "react": "16.0.0-alpha.6",
    "react-markdown": "^2.5.0",
    "react-native": "0.44.0",
    "react-native-animatable": "1.2.0",
    "react-native-config": "0.4.2",
    "react-native-device-info": "0.10.2",
    "react-native-drawer": "2.3.0",
    "react-native-htmlview": "0.9.0",
    "react-native-i18n": "1.0.0",
    "react-native-linear-gradient": "^2.0.0",
    "react-native-photo-view": "^1.2.0",
    "react-native-router-flux": "3.39.1",
    "react-native-scrollable-tab-view": "*",
    "react-native-share": "^1.0.20",
    "react-native-vector-icons": "4.1.1",
    "react-navigation": "^1.0.0-beta.9",
    "react-redux": "5.0.4",
    "redux": "3.6.0",
    "redux-persist": "4.6.0",
    "redux-saga": "0.15.3",
    "reduxsauce": "0.4.1",
    "seamless-immutable": "7.1.2"
  },
  "devDependencies": {
    "ava": "^0.18.2",
    "babel-eslint": "^7.1.1",
    "babel-preset-es2015": "^6.18.0",
    "enzyme": "^2.6.0",
    "husky": "^0.13.1",
    "ignite-animatable": "^0.3.1",
    "ignite-dev-screens": "^2.0.0-beta.9",
    "ignite-i18n": "^0.1.1",
    "ignite-ir-boilerplate-2016": "^0.2.2",
    "ignite-vector-icons": "^0.2.1",
    "mockery": "^2.0.0",
    "nyc": "^10.1.2",
    "react-addons-test-utils": "^15.3.1",
    "react-dom": "^15.4.0",
    "react-native-mock": "^0.3.1",
    "reactotron-apisauce": "^1.7.0",
    "reactotron-react-native": "^1.7.0",
    "reactotron-redux": "^1.7.0",
    "reactotron-redux-saga": "^1.7.0",
    "snazzy": "^6.0.0",
    "standard": "^8.6.0"
  }

@zibs
Copy link

zibs commented May 16, 2017

I'm experiencing a similar issue using 0.44.0 on Android. I am also not using firebase:

Setting a timer for a long period of time, i.e. multiple minutes, is a performance and correctness issue on Android as it keeps the timer module awake, and timers can only be called when the app is in the foreground. See https://github.com/facebook/react-native/issues/12981 for more info. (Saw setTimeout with duration 85000ms)

@deejaygeroso
Copy link

deejaygeroso commented May 17, 2017

Hi there guys.. was wondering Is there any quick work around for this. I'm using:

react-native 0.44 react 16.0.0-alpha.6 feathers-socketio 1.6.0

Its very annoying popping out while I'm developing.. is there anyway I can hide the warning for now??

@aleinnocenzi
Copy link

Same issue here with firebase 3.9.0

@skv-headless
Copy link
Contributor

If it bothers you just add console.ignoredYellowBox = ['Setting a timer'];

@RWOverdijk
Copy link

@skv-headless Opinion incoming. :)

In my opinion that attitude towards problems needs to go away. I've been seeing it pop up more often and it's causing people to ignore warnings. They're warnings. They warn you. Don't ignore them, do something. I've seen apps where all warnings just get ignored, even deprecation notices. Then your app breaks and you're left wondering why.

In this case, you can configure these timeouts and lower them, or setup a different approach of dealing with them. Maybe even poke the maintainers of the libraries and ask them to help come up with a solution.

My advise, is to follow this thread until someone smart comes up with an actual solution, so you can then educate yourself with that answer. And then maybe, in the meantime (if you can't / don't want to teckle the issue) ignore the warnings.

TL;DR;

Yes, you can ignore the warnings temporarily. Just check in every now and then to see what the status is and if there's any action required.

@rigobcastro
Copy link

@imamatory I think is by Rectotron

@imamatory
Copy link

imamatory commented May 19, 2017

I think is by Rectotron

If so, this warning can be simply ignored.
...however my android emulator getting slow sometimes, maybe it is one of reasons.

@rigobcastro
Copy link

@imamatory I hope the real solution is just as easy

@nicolasZZ
Copy link

Hi, I think I found the solution:
Firstly you have to find the following file in your project: libraries/Core/Timers/JSTimer;js
Open it and you just have to change this const MAX_TIMER_DURATION_MS, to increase above your duration, wrote in the end of warning !

@AmroAly
Copy link

AmroAly commented May 23, 2017

@nicolasZZ hi, Thanks for the solution. but Is that a good practice to modify the JSTimer library rather than our own code?

@rigobcastro
Copy link

rigobcastro commented May 23, 2017

@nicolasZZ @AmroAly In my mind this solution is not a good practice because it is very unstable, the best is ignore the alert while the official solution is released.

console.ignoredYellowBox = [
    'Setting a timer'
]

the @skv-headless's solution is good but in my case only works with "enter" between of the parenthesis

@AmroAly
Copy link

AmroAly commented May 25, 2017

@rigobcastro Thanks that hides the annoying alerts.

@chirag04
Copy link
Contributor

@DZuz14 it would be awesome if you could sent a PR or atleast get the conversation started. Thanks 👍

@simpleblack
Copy link

simpleblack commented Jun 8, 2017

hi~
I used socket.io. nodejs and RN44 or RN 45
I saw warning this timers.

I found this solution
reference reactotron PR

personal server nodejs and scoket.io

const express = require('express')
const app = express()
const server = require('http').Server(app)
const io = require('socket.io')(server, {pingTimeout: 30000})

Thanks!

@saeedhei
Copy link

I have a simple react native project for android and I'm using firebase auth with react native for Login and Signup, but I got that yellow error, What should I do? Link of my Question in stackoverslow (https://stackoverflow.com/questions/44603362/setting-a-timer-for-a-long-period-of-time-i-e-multiple-minutes )

@saeedhei
Copy link

saeedhei commented Jun 19, 2017

Thank You @DZuz14
I Got This Error When I run react-native run-android

D:\Projects 2016\Web\Android\mohajerkade-android\android\app\src\main\java\com\mohajerkade\AlarmManagerModule.java:40: error: cannot find symbol
       Intent intent = new Intent(context, AlarmReceiver.class);
                                           ^
  symbol:   class AlarmReceiver
  location: class AlarmManagerModule
1 error
:app:compileDebugJavaWithJavac FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:compileDebugJavaWithJavac'.
> Compilation failed; see the compiler error output for details.

I want to use firebase auth with react native for Login and Signup but I got That yellow error,
1: What is This Module Exactly?
2: Why My Project need This Module?

I asked stackoverflow through this link: (https://stackoverflow.com/questions/44603362/setting-a-timer-for-a-long-period-of-time-i-e-multiple-minutes)
I Reported to Google Firebase Team through this link: (firebase/firebase-js-sdk#97)

@BerndWessels
Copy link
Contributor

@DZuz14 I got the same question as @saeedhei .

Basically we are using the firebase node module that uses these long period timers.

Adding your module is not enough I guess, we actually have to rewrite the code within the firebase node module to use your Alarms rather than Javascript timers, right?

@BerndWessels
Copy link
Contributor

Thanks @DZuz14

So if you look into the npm package called firebase you will find that it uses setTimeout in quite a lot of places. This is OK since the library was written for websites and node.js servers. react-native is kind of a node.js server but it runs on a phone - and as the warning correctly points out - long running Javascript timers are not good in this environment and might drain the battery and have other unwanted side effects.

So I guess we have to convince the Firebase guys actually to change to your approach and distribute a package made especially for react-native.

@DZuz14
Copy link

DZuz14 commented Jun 21, 2017

@BerndWessels https://www.npmjs.com/package/react-native-firebase Have you looked at this? Says it allows firebase to run on the native thread. Not sure how true that is, but might be worth a look. You might even be using that, I'm not sure.

@BerndWessels
Copy link
Contributor

@DZuz14 Thanks, I'll check that out.

@ashishspkota14
Copy link

ashishspkota14 commented Mar 31, 2020

If it bothers you just add console.ignoredYellowBox = ['Setting a timer'];

Where should I add this I tried in Screen file and action file, still not working

@tom-nuyts
Copy link

Just put it somewhere in your App.js file. I put it in my constructor for example.
Don't know if that's the best place, so if anyone knows anything better, I'd like to hear it!

@Remato
Copy link

Remato commented Apr 2, 2020

I think whoever is using Firebase can solve this problem configuring the persistence of app after (signIn / signUp)

*after create a new user, he will be automatically sign in

https://firebase.google.com/docs/auth/web/auth-state-persistence

so... after auth a user, a time variable is created.

the default for firebase is a local. "firebase.auth.Auth.Persistence.LOCAL"

you can change using:
firebase.auth.Auth.Persistence.SESSION
firebase.auth.Auth.Persistence.NONE

or using a localStorage...

@ismdelrod
Copy link

Until they solve it in a definitive way...

import { YellowBox } from "react-native";
import _ from "lodash";
YellowBox.ignoreWarnings(["Setting a timer"]);
const _console = _.clone(console);
console.warn = (message) => {
if (message.indexOf("Setting a timer") <= -1) {
_console.warn(message);
}
};

Once solved, the junk code is removed and voila!

@eightyfive
Copy link

This can be solved with RxJS.

Basically splits your 5200 timer into 5 * 1000 + 200 timers.

import { of, range } from 'rxjs';
import {
  concatMap,
  delay,
  filter,
  mapTo,
  switchMap,
  tap,
} from 'rxjs/operators';

// ~ setTimeout(..., 5200);

const source = of(5200).pipe(
  switchMap(duration => {
    const times = Math.floor(duration / 1000); // = 5
    const remainder = duration % 1000; // = 200

    return range(1, times).pipe(
      concatMap(i => of(i).pipe(delay(1000))),
      tap(console.log),
      filter(i => i === times),
      delay(remainder),
      tap(console.log),
      mapTo('Done !'),
    );
  }),
);

source.subscribe(console.log);

1 // After 1s
2 // After 2s
3 // After 3s
4 // After 4s
5 // After 5s
5 // After 5s + 200 ms
'Done !'

@16oh4
Copy link

16oh4 commented Apr 11, 2020

I think whoever is using Firebase can solve this problem configuring the persistence of app after (signIn / signUp)

*after create a new user, he will be automatically sign in

https://firebase.google.com/docs/auth/web/auth-state-persistence

so... after auth a user, a time variable is created.

the default for firebase is a local. "firebase.auth.Auth.Persistence.LOCAL"

you can change using:
firebase.auth.Auth.Persistence.SESSION
firebase.auth.Auth.Persistence.NONE

or using a localStorage...

Great! How about for Firestore though?

@Remato
Copy link

Remato commented Apr 11, 2020

I think whoever is using Firebase can solve this problem configuring the persistence of app after (signIn / signUp)
*after create a new user, he will be automatically sign in
https://firebase.google.com/docs/auth/web/auth-state-persistence
so... after auth a user, a time variable is created.
the default for firebase is a local. "firebase.auth.Auth.Persistence.LOCAL"
you can change using:
firebase.auth.Auth.Persistence.SESSION
firebase.auth.Auth.Persistence.NONE
or using a localStorage...

Great! How about for Firestore though?

if you are using the old firebase SDK migrate to the firestore admin SDK.

most issues will be solved

https://firebase.google.com/docs/reference/admin

@jphim281
Copy link

jphim281 commented May 14, 2020

Still nobody is able to solve this issue. #shame on you developers ;p

@actuallymentor
Copy link

Still nobody is able to solve this issue. #shame on you developers ;p

Never shame open source contributors. It's a shame this has not been fixed but the source is open, if you know how to solve it I'm sure they would appreciate a pull-request.

That said why on earth is this issue closed.

@RWOverdijk
Copy link

Because it should be fixed in the offending libraries. And the offending libraries don't see a problem with the way it has been implemented.

So nobody will fix it because both sides don't think it's broken.

They can be shamed a little bit for this 😄

@cristianocca
Copy link

Guys, I think this hasn't been "resolved" for the simple reason that it is an extremely bad idea to use long timers.

If you want to run timers in background / events in the future, you will need a native library to do so. This is also specifically difficult on both platforms due to background execution constraints.

@Daanoz
Copy link

Daanoz commented May 25, 2020

Based on @eightyfive 's comment we made a drop in replacement for rxjs6 delay, timeout & interval functions.

Still a question remains, can someone explain if the "Timer module" stays locked when using smaller intervals? Basically just causing the timer to halt when the app is not in the foreground. Or does this just hide the problem?

import { switchMap, concatMap, delay as nativeDelay, mapTo, filter, switchMapTo, repeat } from 'rxjs/operators'
import { range, of, race, throwError, TimeoutError } from 'rxjs'

const TIMER_INTERVAL = 1000
const reactNativeTimer = (duration) => {
  const times = Math.floor(duration / TIMER_INTERVAL)
  const remainder = duration % TIMER_INTERVAL
  if (times < 1) {
    return of(true).pipe(nativeDelay(remainder))
  }
  return range(1, times).pipe(
    concatMap(i => of(i).pipe(nativeDelay(TIMER_INTERVAL))),
    filter(i => i === times),
    nativeDelay(remainder)
  )
}

/**
 * React Native compatible version of delay pipe
 * @param {number} duration in ms
 */
export const delay = (duration) => {
  return (source) => {
    return source.pipe(
      switchMap(next =>
        reactNativeTimer(duration).pipe(mapTo(next))
      )
    )
  }
}

/**
 * React Native compatible version of timeout pipe
 * @param {number} duration in ms
 */
export const timeout = (duration) => {
  return (source) => {
    const timeoutTimer = reactNativeTimer(duration).pipe(
      switchMapTo(throwError(new TimeoutError()))
    )
    return race(source, timeoutTimer)
  }
}

/**
 * React Native compatible version of interval
 * @param {number} duration in ms
 */
export const interval = (duration) => {
  return reactNativeTimer(duration).pipe(
    repeat()
  )
}

@viktorkondin
Copy link

viktorkondin commented May 27, 2020

The funny thing about it, that everyone in this thread discussing workaround to ignore this warning.
When I was redirected to this "issue" from warning, I hoped to find proper documentation, explaining "how?" to handle it and a bit more reasoning about "why?".

#front-end #millennials

@actuallymentor
Copy link

This #millenial would like to point out that:

  • This error does not require documentation, it is self explanatory: you set a long timer.
  • This thread is full of people muting the warning for libraries they do not own (e.g. firebase)
  • As the (productive) discussion in this thread indicates, this is a more complex issue to fix within react-native

That said I will again point out this issue should not be closed, it is marked known-issue and Help Wanted.

@viktorkondin
Copy link

viktorkondin commented May 27, 2020

Millenial answers to millenial :)

This error does not require documentation, it is self explanatory: you set a long timer.

  • Its not an error, its warning (don't ask me about difference, otherwise we have to start deep philosophical conversation :) )
  • Its already pretend to be documented due to provided reference to this ticket, where I need to read tons of comments instead of executive summary (smth like known issue as you already suggested):
    image
  • I would not say it 'self explanatory'. Not enough context provided. Literally, there are two sentences in a description.

That said I will again point out this issue should not be closed, it is marked known-issue and Help Wanted.

Fully agree. It will save time to many people coming from the URL reference in the warning.

Roger-Takeshita added a commit to Roger-Takeshita/React_Native that referenced this issue Jun 12, 2020
    - Auto logout is not very fancy, and should be refactored in the future
    facebook/react-native#12981

* 4_Shop_App/App.js

    - Imported a new component a Wrapper just to have access to our store

* 4_Shop_App/navigation/ShopNavigator.js

    - Create this new component to check first (after the splash screen) if there is a logged user. if not redirect  to Auth screen
    - We need to create this component to have access to our store
    - With the help of useRef hook, we can have access to the properties of the component

* 4_Shop_App/navigation/ShopNavigator.js

    - Added a react component to the side drawer, so we can click to logout
    - Added our StartupScreen as the first screen of the stack

* 4_Shop_App/store/actions/auth.js

    - Changed SIGNUP and LOGIN  to AUTHENTICATE
    - Added a helper function authenticate() to dispatch the (userId, token, expiryTime)
    - Added a logout(), inside this function we call AsyncStorage.removeItem('') to remove the item from our local storage
    - Added a auto logout, it checks for the expiration date, and creates a setTimeout with the remaining time
    - Added a saveDataToStorage() to save the user info in the local storage

* 4_Shop_App/store/reducers/auth.js

    - Removed the cases LOGIN and SIGNUP
    - Added LOGOUT -> basically sets to the initial state

* 4_Shop_App/screens/StartupScreen.js

    - This is the first page of the app
    - It's very fast, and we wont see it (it's just an ActivityIndicator)
    - Basically it checks if there is a user in the local storage
        - if yes, checks the expiration date
            - if everything is ok, redirects to "Shop" screen
        - if no, or expiration date is expired
            - redirects to "Auth" screen
@hardcodet
Copy link

For firebase/firestore users: I simply reverted to using the REST endpoints directly for firestore calls. Since I only needed auth and firestore, that solved my problems rather easily. Snippet here:

https://stackoverflow.com/a/62446792

GuiSanches pushed a commit to GuiSanches/Vigilancia-comunitaria that referenced this issue Jun 20, 2020
…nutes, is a performance and correctness issue on Android as it keeps the timer module awake, and timers can only be called when the app is in the foreground. See facebook/react-native#12981 for more info.

(Saw setTimeout with duration 446716ms)' warning
@Shantnu12345
Copy link

I did it inside App() as follows:

import { YellowBox } from 'react-native';
export default function App(){
YellowBox.ignoreWarnings(['Setting a timer']);
...
...
..
}

Remeber

@hatim85f
Copy link

What I did and it's working with me but still I don't knwo if it's a good practice or not

Navigated to file

node_modules\react-native\Libraries\Core\Timers\JSTimers.js

there is a function const MAX_TIMER_DURATION_MS = 60 * 1000 and I increased the time to be 60 * 100000 and it stopeed appearing

@hamedJenabi
Copy link

Hey there,
This issue is about 3 years old and we still having this warning ;)
as @RWOverdijk said ignoring the warning is not a solution.
Is there already a solution that i missed?

Thanks <3

@Ashoat
Copy link
Contributor

Ashoat commented Jul 2, 2020

Hey all, we're deciding to lock this issue. I know this problem is frustrating for many of you and I want to take the time to help understand why we're making this decision.

How timers work on Android in React Native

React Native has a custom implementation of JS timers like setTimeout. On Android, the way it works is that JS timers get tracked on the native side, and the native side decides when to trigger these timers. This lets React Native be smarter about when timers are triggered, and in particular how they relate to the app lifecycle and rendering.

What happens if I set a timer with a long interval?

Here's my understanding, from reading the code and testing it out a bit:

  1. If you set a long timer, and the app is kept open over the course of the timer being active, the timer should still work.
  2. If you set a long timer, and the phone is backgrounded before the timer finishes, the timer will not be activated until you next open the app.
  3. There is an exception for headless JS tasks, which can keep timers working even if the app is in the background.

Why shouldn't we call timers with long intervals?

I think this is primarily a correctness issue, and less about performance. The issue is that you can't rely on long timers if the user backgrounds your app.

If you don't mind having your timer get activated later when the app is foregrounded again, then I think ignoring the YellowBox warning is a good idea.

On the other hand, if your timer expects to be called for the lifetime of a session, and shouldn't be triggered again on foreground, you'll need to do something to make sure the timer is ignored on foreground.

How do I use third-party libraries like Socket.io and Firebase that set long timers?

I don't know the details of these third-party libraries. It ultimately comes down to the question above. If these third-party libraries will have issues dealing with timers being resolved on foreground, then you'll need to figure out a way to fix that. Some comments above propose ways of doing this for Firebase, for instance. Ultimately, though, you may be better served by relying on a library that's developed specifically with React Native in mind.

On the other hand, if the third-party library has no issues with timers being resolved on foreground, then you can ignore the error.

Why can't setTimeout go through AlarmManager, as suggested initially?

AlarmManager will wake up the app if it's in the background, and we don't think timers like setTimeout should wake up the app. Most of these long timers are set for things like timeouts, and those are only relevant while the app is open. Waking up an app in the background is expensive and should only be done if the developer explicitly has this intention.

At the same time, many people probably have a need for something like this. We could develop an API for it that doesn't go through setTimeout. But because of the Lean Core effort right now we're actually trying to avoid increasing the API surface for React Native. We think this is a task best handled by a third-party library like react-native-background-timer.

Why don't you remove the warning entirely then?

We want developers to be aware of what's going on. If somebody installs a library that relies on the correctness of setTimeout, we think the developer should be aware that we cannot guarantee this correctness. We want to leave it up to individual developers to make the decision of whether this warning matters in their particular use case.

@facebook facebook locked as resolved and limited conversation to collaborators Jul 2, 2020
@Ashoat Ashoat removed Help Wanted :octocat: Issues ideal for external contributors. 📮Known Issues This indicates an issue that refers to a bug or limitation of RN that is not currently being handled labels Jul 2, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests