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

[Timer] React Events not fired if the device is locked and screen turned off. #1282

Closed
mintuz opened this issue May 14, 2015 · 52 comments
Closed
Labels
Resolution: Locked This issue was locked by the bot.

Comments

@mintuz
Copy link

mintuz commented May 14, 2015

If you have a delegate method that is fired every X seconds in the background when the device is locked. The delegate method is fired but the event is not. The following code

This log message Is triggered when the device is locked and screen is inactive.

NSLog(@"userUUID found %@", userUUID);

This event is triggered if the device is locked but the screen needs to be active.

[self.bridge.eventDispatcher sendDeviceEventWithName:@"nearByUserFound" body:@{@"userUUID":userUUID}];
//
//  ProximityManager.m
//  Proximity
//
//  Created by Adam Bulmer on 07/05/2015.
//  Copyright (c) 2015 Adam Bulmer All rights reserved.
//

#import <Foundation/Foundation.h>
#import "ProximityManager.h"

@implementation ProximityManager

  @synthesize bridge = _bridge;

// MARK: React Native Bridge

  RCT_EXPORT_MODULE()

  RCT_EXPORT_METHOD(startAdvertising:(NSString *)appUUID
                    userUUID:(NSString*)userUUID) {
    self.proximity = [[Proximity alloc] initWithAppUUID:appUUID userUUID:userUUID delegate:self];
  }

  // MARK: ProximityDelegate Callbacks.

  - (void)nearbyUserFound:(NSString *)userUUID {
    NSLog(@"userUUID found %@", userUUID);
    [self.bridge.eventDispatcher sendDeviceEventWithName:@"nearByUserFound" body:@{@"userUUID":userUUID}];
  }

@end
@vjeux
Copy link
Contributor

vjeux commented May 14, 2015

cc @nicklockwood and @a2

@ide
Copy link
Contributor

ide commented May 14, 2015

I agree this is a bug. Timer events - except maybe rAF - should run in the background.

@nicklockwood
Copy link
Contributor

cc @tadeuzagallo - maybe we're running the displaylink in the wrong mode? I thought it was using commonModes already, but maybe not.

@ide
Copy link
Contributor

ide commented May 14, 2015

@nicklockwood the timers are also explicitly paused when the app is backgrounded #1218 (comment)

Haven't explored to see if the run loop is a confounding factor but the timer code is part of the problem for sure.

@nicklockwood
Copy link
Contributor

Oh, yeah. That would do it. I think timers were always envisioned as being used for animation-related purposes, but it seems like we should add an explicit "pauseInBackground" property or something rather than pausing all timers by default.

@jaygarcia
Copy link
Contributor

I've experienced this via command-center generated events. However, my experience, when the device is locked, events publish fine when the phone is locked and the screen is on. However, when the phone is locked and the screen is off, events actually queue up. So, if i hit next next next, my player will move 3 songs forward in the playlist.

The flow is:
Command Center event -> my bridge impl class -> React JS code.

@mintuz
Copy link
Author

mintuz commented May 20, 2015

@jaygarcia If you put a NSLog in your bridge where the event is also triggered, does the output display but the event not fired if the screen is turned off? I'm not having the events queue up, they just don't fire.

@jaygarcia
Copy link
Contributor

OK. @mintuz @nicklockwood @ide

So in doing this testing, i can tell you that when events are generated via CommandCenter, they queue up. The runloop seems to freeze (to be expected) when the phone sleeps.

Command center implementation: https://github.com/ModusCreateOrg/react-native-mod-player/blob/master/kgmp/iOS/ModusCreate/MCModPlayerInterface.m#L196

Event listener: https://github.com/ModusCreateOrg/react-native-mod-player/blob/master/kgmp/jsx/player/RandomPlayer.js#L287

Examples below

registering the event handler (JS)


    componentWillMount : function() {
        this.commandCenterEventHandler = RCTDeviceEventEmitter.addListener(
            'commandCenterEvent',
            this.onCommandCenterEvent
        );
    },

onCommandCenterEvent

   onCommandCenterEvent : function(event) {

        console.log('onCommandCenterEvent ' + event.eventType);
                // debugger;

        switch(event.eventType) {
            case 'play' :
               this.playTrack();
            break;

            case 'pause' :
                this.pauseTrack();
            break;

            case 'nextTrack' :
                this.nextTrack();
            break;

            case 'previousTrack' :
                this.previousTrack();
            break;

            case 'seekBackward' : 
                // TODO
            break;

            case 'seekForward' : 
                // TODO
            break;

            default:

            break; 
        }    
    },

XCode console phone is awake

2015-05-26 15:20:13.422 KGMP[1982:589602] nextTrackCommand
2015-05-26 15:20:13.435 [info][tid:com.facebook.React.JavaScript] "onCommandCenterEvent nextTrack"
2015-05-26 15:20:15.382 KGMP[1982:589602] nextTrackCommand
2015-05-26 15:20:15.386 [info][tid:com.facebook.React.JavaScript] "onCommandCenterEvent nextTrack"
2015-05-26 15:20:19.607 KGMP[1982:589602] nextTrackCommand
2015-05-26 15:20:19.622 [info][tid:com.facebook.React.JavaScript] "onCommandCenterEvent nextTrack"
2015-05-26 15:20:21.794 KGMP[1982:589602] previousTrackCommand
2015-05-26 15:20:21.806 [info][tid:com.facebook.React.JavaScript] "onCommandCenterEvent previousTrack"
2015-05-26 15:20:23.645 KGMP[1982:589602] previousTrackCommand
2015-05-26 15:20:23.657 [info][tid:com.facebook.React.JavaScript] "onCommandCenterEvent previousTrack"
2015-05-26 15:20:25.367 KGMP[1982:589602] previousTrackCommand
2015-05-26 15:20:25.374 [info][tid:com.facebook.React.JavaScript] "onCommandCenterEvent previousTrack"

XCode console phone is sleeping

2015-05-26 15:18:29.914 KGMP[1982:589602] nextTrackCommand
2015-05-26 15:18:30.907 KGMP[1982:589602] nextTrackCommand
2015-05-26 15:18:32.044 KGMP[1982:589602] nextTrackCommand
2015-05-26 15:18:33.361 KGMP[1982:589602] previousTrackCommand
2015-05-26 15:18:34.623 KGMP[1982:589602] previousTrackCommand
2015-05-26 15:18:35.717 KGMP[1982:589602] previousTrackCommand
2015-05-26 15:18:37.054 [info][tid:com.facebook.React.JavaScript] "onCommandCenterEvent nextTrack"
2015-05-26 15:18:37.055 [info][tid:com.facebook.React.JavaScript] "onCommandCenterEvent nextTrack"
2015-05-26 15:18:37.055 [info][tid:com.facebook.React.JavaScript] "onCommandCenterEvent nextTrack"
2015-05-26 15:18:37.056 [info][tid:com.facebook.React.JavaScript] "onCommandCenterEvent previousTrack"
2015-05-26 15:18:37.057 [info][tid:com.facebook.React.JavaScript] "onCommandCenterEvent previousTrack"
2015-05-26 15:18:37.057 [info][tid:com.facebook.React.JavaScript] "onCommandCenterEvent previousTrack"

@jaygarcia
Copy link
Contributor

Here's why this is an issue for the type of app I'm currently writing:

Audio applications need to be able to:

  • run in the background
  • respond to command center events (Locked screen, command center drawer, USB remote control or even head phone remotes)

This application was designed, like many (if not all) RN applications, to have the controller logic inside of the JSX/JSCore layer.

Correct me if i'm wrong, but I think unless this specific issue can get resolved, then any type of application that requires background responsiveness cannot use React Native unless the controller logic is duplicated in the Objective C layer.

=)

@jaygarcia
Copy link
Contributor

Lastly, if anyone plans on cloning that repo, know that it's HUGE atm. It's got > 4.3K Mod files, which will be removed once i configure the code to auto-download a zip file.

@brentvatne brentvatne changed the title bug - React Events not fired if the device is locked and screen turned off. [Timer] React Events not fired if the device is locked and screen turned off. May 29, 2015
@jaygarcia
Copy link
Contributor

A quick bump. this is still an issue.

@tadeuzagallo
Copy link
Contributor

I'll do a minor refactor on timers soon(ish) to address #1539, I'll try to look into it as well.

@jaygarcia
Copy link
Contributor

👍

@strefethen
Copy link

Hi @tadeuzagallo curious if there is any update/timeline on this issue?

Thanks!

@DaleLJefferson
Copy link
Contributor

I have just run into this issue attempting to use beacon region monitoring.

When the app is open I receive the event and I post a local notification to the user, when the app is in the background nothing happens until the screen come back on and all the notifications get fired.

Is there a work around for this, or is there a better way of doing this?

@jacobrosenthal
Copy link
Contributor

I believe #1660 is related

@mintuz
Copy link
Author

mintuz commented Aug 18, 2015

Any update on this, sorry for the spam. Just seems to have gone quiet.

@christopherdro
Copy link
Contributor

@dalejefferson I think beacon monitoring is a bit different. See #1660

@jaygarcia
Copy link
Contributor

After doing some research, I've determined that I needed to move core controller logic for my music app to the ObjC layer. Things like the RN runloop sleeping and queueing events was breaking things. I couldn't figure out if there was a way to signal the Runloop to awaken from suspension.

I'm happy w/ these changes and have realized that this isn't an issue with React Native, but just the way iOS handles runloops that aren't required for background processing.

@seidtgeist
Copy link

@jaygarcia Do you think it's possible to create a short-lived JSC context with react-native modules to run the logic in JavaScript?

@timfpark
Copy link
Contributor

Has there been any more thinking on this? I'm trying to decide if I need to build the Obj-C controller logic for my app.

@davidgustys
Copy link

+1

@DaleLJefferson
Copy link
Contributor

@timfpark I did got hold of the JSC context and ran my javascript logic directly while React was asleep. It was a messy hack but it allow me to keep all my code in js land.

@timfpark
Copy link
Contributor

Interesting! Do you happen to have a gist for how you did that for folks on
this thread to use as a workaround until this issue is closed?

On Thu, Oct 22, 2015 at 12:04 PM, Dale Jefferson notifications@github.com
wrote:

@timfpark https://github.com/timfpark I did got hold of the JSC context
and ran my javascript logic directly while React was asleep. It was a messy
hack but it allow me to keep all my code in js land.


Reply to this email directly or view it on GitHub
#1282 (comment)
.

@DaleLJefferson
Copy link
Contributor

@timfpark Here is my a Gist of the important code.
https://gist.github.com/DaleJefferson/96f72c4081ef0765a2fb

I have not tried this with anything newer than React Native 0.8.0, but in theory it should still work.

@jamesfzhang
Copy link
Contributor

@dalejefferson Do you mind giving an overview of how the code in that gist works and fixes the problem? Thanks :)

@jamesfzhang
Copy link
Contributor

@toblux Good catch, I updated the title to correctly reflect the issue, and that changed the slug of the URL without redirecting (bad! :p)

@mkonicek
Copy link
Contributor

Trying to understands some context: Why do you need this? What work do you want to do in the background? Can you use AppStateIOS to do the work once the app comes to foreground again?

timer events - except maybe rAF - should run in the background.

I don't think we do that on Android.

@MossP
Copy link

MossP commented Nov 17, 2015

@mkonicek I think the beacon example mentioned above by @dalejefferson is a prime example of where the processing would need to be done in the background.

Preferred behaviour:
user gets within range of beacon
event gets triggered in app
app fire local notification
user see this

Delayed processing would result in:
User gets in range of beacon
event sent to app.
user walks away
user opens app later, then gets a message telling them about the beacon that they are no longer near.

@seidtgeist
Copy link

@mkonicek My use case, sorry if it's unrelated:

  • Background push notification notifies app of new data
  • App downloads new data using JS thread (fetch & AsyncStorage)
  • JS thread notifies system that download of new data was successful

I did this in Obj-C on iOS with an AWS Obj-C SDK, but it would be much more convenient to do it in JavaScript where all the other code is.

@DaleLJefferson
Copy link
Contributor

@mkonicek I had to use my hack (in the gist above) on several projects now.

Features we regularly use that need to happen in the background.

  • Geofence trigger
  • Beacon trigger
  • Background download
  • Silent push notifications

Example:

Our server sends out a silent push that the proximity config has been updated.
Javascript code needs to download config. (While in background)
User gets in range of Geofence, Javascript needs to read config, execute business logic and present user with message (While in background)
Same for Beacon

@toblux
Copy link

toblux commented Nov 17, 2015

Upgrading to 0.14 fixed this issue for me.

@mkonicek
Copy link
Contributor

Thanks for the use cases guys, totally makes sense.

@jamesfzhang
Copy link
Contributor

@toblux I tried releases 0.14, 0.14.1, 0.14.2 and I'm still experiencing this issue.

EDIT: This problem seems to have been fixed in 0.14.

@seidtgeist
Copy link

@toblux @jamesfzhang Heya, just wondering how exactly the issue is fixed for you since 0.14 – Does your JavaScript event handler code actually run when the app is in the background?

@jamesfzhang
Copy link
Contributor

@ehd I'm not sure exactly how it was fixed but the event handler code do run when the app runs in the background. I use the remote controls (like play/pause a song) to test the event handlers.

@seidtgeist
Copy link

@jamesfzhang Thank you, good to know! Perhaps it works for remote controls but not for push notifications. Maybe also a DeviceEventEmitter event I'm missing. I'll try to get it working.

@jamesfzhang
Copy link
Contributor

@ehd If it works for remote, I imagine it must work for push. How are you subscribing to the push events? For the remote controls, I subscribe to them like so & dispatch an app event:

let commandCenter = MPRemoteCommandCenter.sharedCommandCenter()
commandCenter.playCommand.addTarget(self, action: Selector("didReceivePlayCommand"))

@seidtgeist
Copy link

@jamesfzhang Yeah, I think you're right. There's no reason why it shouldn't work.

I'm subscribing to push events via PushNotificationIOS.addEventListener('notification', ...), but when the notification is a silent background notification (indicating a download) it won't go through.

So now I'm going to implement application:handleEventsForBackgroundURLSession:completionHandler: and send a notification from there. Also: I can't wait to turn the little Obj-C I have into Swift 😱

And thank you for getting back, I really appreciate your assistance!

Edit: Actually implementing didReceiveRemoteNotification:fetchCompletionHandler: first

@jamesfzhang
Copy link
Contributor

Sounds like a good plan, good luck!

@seidtgeist
Copy link

Holy cow, I think it works and it was really simple:

Instead of

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification
{
  [RCTPushNotificationManager didReceiveRemoteNotification:notification];
}

[edit] adding a additional delegate method works:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler
{
  [RCTPushNotificationManager didReceiveRemoteNotification:notification];
  handler(UIBackgroundFetchResultNewData);
}

I can actually fetch data from the network in JS while the phone is locked and log the content to the console. I hope I'm not dreaming or something 😄

@yonahforst
Copy link

@ehd - did it work even though you called the completion handler right away? and wait until whatever operation you were doing completed?

I'm able to perform network operations if I retain the completion handler and not call it right away. But then I don't know how signal from react back to iOS that the operation has completed

@seidtgeist
Copy link

@joshblour In fact it only works when the phone is connected to power (and possibly on wifi). So we need a way of retaining the handler and calling it from JS somehow, don't we?

@yonahforst
Copy link

@ehd thats exactly right. retaining is easy enough (although its tricky because ios can call the delegate multiple times and expects you to call the completion handler for each one, so you need some kind of queue) But i have no idea how to export an AppDelegate method to JS...

@yonahforst
Copy link

here's my current hacky workaround:

-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {

  [RCTPushNotificationManager didReceiveRemoteNotification:userInfo];

  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    completionHandler(UIBackgroundFetchResultNewData);
  });
}

@niftylettuce
Copy link
Contributor

@jaygarcia Hey - do you have code sample of how you achieved this? When phone off / screen locked - you hit a button and then RN code works while in background?

@niftylettuce
Copy link
Contributor

@joshblour how would we rewrite that for Android per #1282 (comment)?

@oney
Copy link

oney commented May 19, 2016

@nicklockwood agree with idea of pauseInBackground. We should pause animations in the background, but it should make developers decide to pause timers(setTimeout and setInterval) or not.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests