react-native/docs/headless-js-android.md

150 lines
5.6 KiB
Markdown
Raw Normal View History

---
id: headless-js-android
title: Headless JS
layout: docs
category: Guides (Android)
permalink: docs/headless-js-android.html
next: signed-apk-android
Add props for overriding native component Summary: Opening a new PR for #10946 (see discussion there). This PR builds upon #14775 (iOS ViewManager inheritance) and #14261 (more extensible Android WebView). **Motivation** When `WebView.android.js` and `WebView.ios.js` use `requireNativeComponent`, they are hard-coded to require `RCTWebView`. This means if you want to re-use the same JS-logic, but require a custom native WebView-implementation, you have to duplicate the entire JS-code files. The same is true if you want to pass through any custom events or props, which you want to set on the custom native `WebView`. What I'm trying to solve with this PR is to able to extend native WebView logic, and being able to re-use and extend existing WebView JS-logic. This is done by adding a new `nativeConfig` prop on WebView. I've also moved the extra `requireNativeComponent` config to `WebView.extraNativeComponentConfig` for easier re-use. **Test plan** jacobp100 has been kind enough to help me with docs for this new feature. So that is part of the PR and can be read for some information. I've also created an example app which demonstrates how to use this functionality: https://github.com/cbrevik/webview-native-config-example If you've implemented the native side as in the example repo above, it should be fairly easy to use from JavaScript like this: ```javascript import React, { Component, PropTypes } from 'react'; import { WebView, requireNativeComponent, NativeModules } from 'react-native'; const { CustomWebViewManager } = NativeModules; export default class CustomWebView extends Component { static propTypes = { ...WebView.propTypes, finalUrl: PropTypes.string, onNavigationCompleted: PropTypes.func, }; _onNavigationCompleted = (event) => { const { onNavigationCompleted } = this.props; onNavigationCompleted && onNavigationCompleted(event); } render() { return ( <WebView {...this.props} nativeConfig={{ component: RCTCustomWebView, props: { finalUrl: this.props.finalUrl, onNavigationCompleted: this._onNavigationCompleted, }, viewManager: CustomWebViewManager }} /> ); } } const RCTCustomWebView = requireNativeComponent( 'RCTCustomWebView', CustomWebView, WebView.extraNativeComponentConfig ); ``` As you see, you require the custom native implementation at the bottom, and send in that along with any custom props with the `nativeConfig` prop on the `WebView`. You also send in the `viewManager` since iOS requires that for `startLoadWithResult`. **Discussion** As noted in the original PR, this could in principle be done with more React Native components, to make it easier for the community to re-use and extend native components. Closes https://github.com/facebook/react-native/pull/15016 Differential Revision: D5701280 Pulled By: hramos fbshipit-source-id: 6c3702654339b037ee81d190c623b8857550e972
2017-09-19 22:49:44 +00:00
previous: custom-webview-android
---
Headless JS is a way to run tasks in JavaScript while your app is in the background. It can be used, for example, to sync fresh data, handle push notifications, or play music.
## The JS API
A task is a simple async function that you register on `AppRegistry`, similar to registering React applications:
```js
AppRegistry.registerHeadlessTask('SomeTaskName', () => require('SomeTaskName'));
```
Then, in `SomeTaskName.js`:
```js
module.exports = async (taskData) => {
// do stuff
}
```
You can do anything in your task as long as it doesn't touch UI: network requests, timers and so on. Once your task completes (i.e. the promise is resolved), React Native will go into "paused" mode (unless there are other tasks running, or there is a foreground app).
## The Java API
Yes, this does still require some native code, but it's pretty thin. You need to extend `HeadlessJsTaskService` and override `getTaskConfig`, e.g.:
```java
public class MyTaskService extends HeadlessJsTaskService {
@Override
protected @Nullable HeadlessJsTaskConfig getTaskConfig(Intent intent) {
Bundle extras = intent.getExtras();
if (extras != null) {
return new HeadlessJsTaskConfig(
"SomeTaskName",
Arguments.fromBundle(extras),
5000, // timeout for the task
false // optional: defines whether or not the task is allowed in foreground. Default is false
);
}
return null;
}
}
```
Then add the service to your `AndroidManifest.xml` file:
```
<service android:name="com.example.MyTaskService" />
```
Now, whenever you [start your service][0], e.g. as a periodic task or in response to some system event / broadcast, JS will spin up, run your task, then spin down.
Example:
```java
Intent service = new Intent(getApplicationContext(), MyTaskService.class);
Bundle bundle = new Bundle();
bundle.putString("foo", "bar");
service.putExtras(bundle);
getApplicationContext().startService(service);
```
## Caveats
* By default, your app will crash if you try to run a task while the app is in the foreground. This is to prevent developers from shooting themselves in the foot by doing a lot of work in a task and slowing the UI. You can pass a fourth `boolean` argument to control this behaviour.
* If you start your service from a `BroadcastReceiver`, make sure to call `HeadlessJsTaskService.acquireWakeLockNow()` before returning from `onReceive()`.
## Example Usage
Service can be started from Java API. First you need to decide when the service should be started and implement your solution accordingly. Here is a simple example that reacts to network connection change.
Following lines shows part of Android manifest file for registering broadcast receiver.
```xml
<receiver android:name=".NetworkChangeReceiver" >
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
```
Broadcast receiver then handles intent that was broadcasted in onReceive function. This is a great place to check whether your app is on foreground or not. If app is not on foreground we can prepare our intent to be started, with no information or additional information bundled using `putExtra` (keep in mind bundle can handle only parcelable values). In the end service is started and wakelock is acquired.
```java
public class NetworkChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
/**
This part will be called everytime network connection is changed
e.g. Connected -> Not Connected
**/
if (!isAppOnForeground((context))) {
/**
We will start our service and send extra info about
network connections
**/
boolean hasInternet = isNetworkAvailable(context);
Intent serviceIntent = new Intent(context, MyTaskService.class);
serviceIntent.putExtra("hasInternet", hasInternet);
context.startService(serviceIntent);
HeadlessJsTaskService.acquireWakeLockNow(context);
}
}
private boolean isAppOnForeground(Context context) {
/**
We need to check if app is in foreground otherwise the app will crash.
http://stackoverflow.com/questions/8489993/check-android-application-is-in-foreground-or-not
**/
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> appProcesses =
activityManager.getRunningAppProcesses();
if (appProcesses == null) {
return false;
}
final String packageName = context.getPackageName();
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.importance ==
ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
appProcess.processName.equals(packageName)) {
return true;
}
}
return false;
}
public static boolean isNetworkAvailable(Context context) {
ConnectivityManager cm = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
return (netInfo != null && netInfo.isConnected());
}
}
```
[0]: https://developer.android.com/reference/android/content/Context.html#startService(android.content.Intent)