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

Headless JS Android -- detachViewFromInstance() prevents Background Tasks if App is closed #16267

Closed
trevorwhealy opened this issue Oct 10, 2017 · 11 comments
Labels
Stale There has been a lack of activity on this issue and it may be closed soon.

Comments

@trevorwhealy
Copy link

trevorwhealy commented Oct 10, 2017

When the app is closed, detachViewFromInstance is invoked by detachRootView and from then on, all the native BroadcastReceivers are disabled, meaning that background functionality and services are not truly possible with closed applications.

Background services are a major component of certain applications, and if the native Android behavior (the BroadcastReceivers) are severed as part of the application closing, then..

The headlessJS bridge is not useful if the "background service" only functions if the app is minimized, but not if it is truly closed.

ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java

private void detachViewFromInstance(
      ReactRootView rootView,
      CatalystInstance catalystInstance) {
    Log.d(ReactConstants.TAG, "ReactInstanceManager.detachViewFromInstance()");
    UiThreadUtil.assertOnUiThread();
    catalystInstance.getJSModule(AppRegistry.class)
        .unmountApplicationComponentAtRootTag(rootView.getId());
  }

  ...

  /**
   * Detach given {@param rootView} from current catalyst instance. It's safe to call this method
   * multiple times on the same {@param rootView} - in that case view will be detached with the
   * first call.
   */
  @ThreadConfined(UI)
  public void detachRootView(ReactRootView rootView) {
    UiThreadUtil.assertOnUiThread();
    if (mAttachedRootViews.remove(rootView)) {
      ReactContext currentContext = getCurrentReactContext();
      if (currentContext != null && currentContext.hasActiveCatalystInstance()) {
        detachViewFromInstance(rootView, currentContext.getCatalystInstance());
      }
    }
  }

Is this a bug report?

Yes

Have you read the Contributing Guidelines?

Yes

Environment

react-native info

Environment:
OS: macOS Sierra 10.12.3
Node: 8.5.0
Yarn: Not Found
npm: 5.3.0
Watchman: 4.9.0
Xcode: Xcode 8.3.3 Build version 8E3004b
Android Studio: 2.3 AI-162.4069837

Packages: (wanted => installed)
react: 16.0.0-beta.5 => 16.0.0-beta.5
react-native: 0.49.2 => 0.49.2

Target Platform: Android Emulator - Nexus_6_API_23

Expected Behavior

The behavior in background mode would be consistent with behavior when the app is truly closed.

Actual Behavior

Logs come from background mode, but no logs come from the closed app.

@vjeranc
Copy link
Contributor

vjeranc commented Oct 10, 2017

Seems to be a design issue. #15960

NetInfo also won't work in background because listeners are unregistered when activity is destroyed.

Other react-native modules (outside of the core library) seem to use similar patterns.

@trevorwhealy
Copy link
Author

trevorwhealy commented Oct 10, 2017

@vjeranc that's really unfortunate to hear. Albeit a personal fork of react-native, I'm happy to hear that you were able to develop a bypass. Could you please share how you managed to remove the unregistering logic so that I may apply the same patches and continue working and testing this too?

@vjeranc
Copy link
Contributor

vjeranc commented Oct 10, 2017

@trevorwhealy Initial workaround is to have a background task that never ends (Promise that never resolves). Task can be terminated from native code explicitly. This will keep the JS bridge alive after activity is destroyed and calls to other JS tasks should work.

@stale
Copy link

stale bot commented Dec 9, 2017

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Maybe the issue has been fixed in a recent release, or perhaps it is not affecting a lot of people. If you think this issue should definitely remain open, please let us know why. Thank you for your contributions.

@stale stale bot added the Stale There has been a lack of activity on this issue and it may be closed soon. label Dec 9, 2017
@stale stale bot closed this as completed Dec 16, 2017
@vjeranc
Copy link
Contributor

vjeranc commented Dec 16, 2017

Good bot. Closing an issue that still exists!

@BlackStef
Copy link

Any Idea? My app crash with a background task (or background job) if I kill it...

@trevorwhealy
Copy link
Author

@BlackStef yeah I'm not entirely sure that Headless JS is reliable/mature enough yet to say. I don't think it natively supports a true background service experience since it kills the listeners when the app is closed. It is great for a minimized app experience, but to be honest, that is not something I think most people consider being "in the background", hence the confusion.

@facebook-github-bot please reopen this issue it does not appear to be stale any longer.

@gervasioaraujo
Copy link

Hi guys, I fixed this problem by following this tutorial:
https://fabcirablog.weebly.com/blog/creating-a-never-ending-background-service-in-android

@trevorwhealy
Copy link
Author

@gervasioaraujo this is really cool thanks for sharing !

@kmvkrish
Copy link

kmvkrish commented Oct 26, 2018

I tried to register the Headless JS task in the index.js file after registering the component like:
AppRegistry.registerHeadlessTask(key, func). With this, the app is listening to service calls even after app is killed.

`
import { AppRegistry } from 'react-native';

import App from './App';

import { name as appName } from './app.json';

AppRegistry.registerComponent(appName, () => App);

const bgTask = async (data) => {
console.debug({ "serviceData": data });
};

AppRegistry.registerHeadlessTask("BGTask", () => bgTask);
`

But, when the task registration is done inside any component, and the app is killed, then the react native application context is undefined, and no task is being called.

`
import React, {Component} from 'react';
import {AppRegistry, Platform, StyleSheet, View, TouchableNativeFeedback,
TextInput, ToastAndroid, Text} from 'react-native';

export default class App extends Component {
constructor(props) {
super(props);
this.state = {
defaultInterval: '1000'
};
}

handleChange = (text) => {
this.setState({
defaultInterval: text
});
}

handlePress = () => {
const bgTask = async (data) => {
console.debug({"serviceData": data});
ToastAndroid.show("" + data.hasInternet, ToastAndroid.LONG);
};
AppRegistry.registerHeadlessTask("BGTask", () => bgTask);

}

render() {
return (
<View style={styles.container}> <TextInput placeholder="Interval in MilliSeconds" value={this.state.defaultInterval} onChangeText={this.handleChange} /> <TouchableNativeFeedback onPress={this.handlePress}> <View> <Text>Send Alarm</Text> </View> </TouchableNativeFeedback> </View>
);
}
}

`

@facebook facebook locked as resolved and limited conversation to collaborators Dec 16, 2018
@cpojer
Copy link
Contributor

cpojer commented Feb 12, 2019

Heads up: we moved react-native-netinfo into its own repository, which should allow for making changes and fixes much faster. If you'd like to create the same issue there, please go for it!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Stale There has been a lack of activity on this issue and it may be closed soon.
Projects
None yet
Development

No branches or pull requests

6 participants