├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── android ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── codegulp │ └── invokeapp │ ├── RNInvokeApp.java │ └── ReactNativeInvokeAppPackage.java ├── index.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # OSX 3 | # 4 | .DS_Store 5 | 6 | # node.js 7 | # 8 | node_modules/ 9 | npm-debug.log 10 | yarn-error.log 11 | 12 | 13 | # Xcode 14 | # 15 | build/ 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | xcuserdata 25 | *.xccheckout 26 | *.moved-aside 27 | DerivedData 28 | *.hmap 29 | *.ipa 30 | *.xcuserstate 31 | project.xcworkspace 32 | 33 | 34 | # Android/IntelliJ 35 | # 36 | build/ 37 | .idea 38 | .gradle 39 | local.properties 40 | *.iml 41 | 42 | # BUCK 43 | buck-out/ 44 | \.buckd/ 45 | *.keystore 46 | 47 | # JAVA 48 | .project 49 | .classpath 50 | .settings 51 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Generated files 2 | android/bin/ 3 | android/gen/ 4 | android/out/ 5 | 6 | # Gradle files 7 | android/.gradle/ 8 | android/build/ 9 | android/app/build/ 10 | 11 | # Local configuration file (sdk path, etc) 12 | local.properties 13 | 14 | # Proguard folder generated by Eclipse 15 | android/proguard/ 16 | 17 | # Log Files 18 | *.log 19 | 20 | # Android Studio Navigation editor temp files 21 | android/.navigation/ 22 | 23 | # Android Studio captures folder 24 | android/captures/ 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ganapathy S 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # React Native Invoke App 3 | 4 | [![npm version](https://badge.fury.io/js/react-native-invoke-app.svg)](https://badge.fury.io/js/react-native-invoke-app) 5 | 6 | [Headless JS](https://facebook.github.io/react-native/docs/headless-js-android.html) is a way to run background tasks in a React Native app. Sometimes we may want to open the app from background task (Headless JS). You can use this module to bring your app to foreground in all the following three cases. 7 | 8 | - App is in foreground 9 | - App is in background 10 | - App is not running 11 | 12 | ## Installation 13 | 14 | ``` 15 | $ npm install --save react-native-invoke-app 16 | # if your react-native version is <0.60 17 | $ react-native link react-native-invoke-app 18 | ``` 19 | 20 | ## Usage 21 | ```javascript 22 | import invokeApp from 'react-native-invoke-app'; 23 | 24 | // Within your headless function 25 | invokeApp(); 26 | ``` 27 | 28 | ## Advanced usage 29 | 30 | You can pass an object to `invokeApp` method to pick it from [DeviceEventEmitter](https://facebook.github.io/react-native/docs/native-modules-android.html#sending-events-to-javascript) by listening to `appInvoked` event. 31 | 32 | Example: 33 | 34 | ```javascript 35 | const yourObject = { route: 'Dashboard' }; 36 | 37 | invokeApp({ 38 | data: yourObject, 39 | }) 40 | ``` 41 | 42 | ### Use case 43 | 44 | Let's say you want to navigate to dashboard screen of the app after a specific task is completed. You can acheive it like, 45 | 46 | ```javascript 47 | import React, { Component } from 'react'; 48 | import { AppRegistry, DeviceEventEmitter, Text, View } from 'react-native'; 49 | import { createStackNavigator } from 'react-navigation'; 50 | import invokeApp from 'react-native-invoke-app'; 51 | 52 | import Dashboard from './dashboard'; 53 | 54 | class App extends Component { 55 | componentWillMount() { 56 | DeviceEventEmitter.addListener('appInvoked', (data) => { 57 | const { route } = data; 58 | 59 | // Using react-navigation library for navigation. 60 | this.props.navigation.navigate(route); 61 | }); 62 | } 63 | 64 | render() { 65 | return ( 66 | 67 | 68 | This is Home screen. 69 | 70 | 71 | ) 72 | } 73 | } 74 | 75 | const appStack = () => { 76 | const Stack = createStackNavigator({ 77 | App, 78 | Dashboard, 79 | }); 80 | 81 | return 82 | } 83 | 84 | const notificationActionHandler = async (data) => { 85 | // Your background task 86 | const yourObject = { route: 'Dashboard' }; 87 | 88 | invokeApp({ 89 | data: yourObject, 90 | }) 91 | } 92 | 93 | AppRegistry.registerHeadlessTask( 94 | 'RNPushNotificationActionHandlerTask', () => notificationActionHandler, 95 | ); 96 | 97 | AppRegistry.registerComponent('testProject', () => appStack); 98 | 99 | ``` 100 | 101 | ## Extra step needed when app is not running 102 | 103 | Event listener will work fine when your app is in background or foreground. If it is not running, to capture the first event we need to do some extra work. Make the following changes in your `MainActivity.java` file of React Native app, 104 | 105 | ```diff 106 | package com.yourpackage; 107 | 108 | +import android.os.Bundle; 109 | import com.facebook.react.ReactActivity; 110 | +import com.codegulp.invokeapp.RNInvokeApp; 111 | 112 | public class MainActivity extends ReactActivity { 113 | /** 114 | * Returns the name of the main component registered from JavaScript. 115 | * This is used to schedule rendering of the component. 116 | */ 117 | @Override 118 | protected String getMainComponentName() { 119 | return "testProject"; 120 | } 121 | 122 | + @Override 123 | + protected void onCreate(Bundle savedInstanceState) { 124 | + super.onCreate(savedInstanceState); 125 | + RNInvokeApp.sendEvent(); 126 | + } 127 | } 128 | ``` 129 | 130 | ### License 131 | 132 | MIT 133 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | def safeExtGet = {prop, fallback -> 2 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 3 | } 4 | 5 | buildscript { 6 | // The Android Gradle plugin is only required when opening the android folder stand-alone. 7 | // This avoids unnecessary downloads and potential conflicts when the library is included as a 8 | // module dependency in an application project. 9 | if (project == rootProject) { 10 | repositories { 11 | google() 12 | jcenter() 13 | } 14 | 15 | dependencies { 16 | classpath("com.android.tools.build:gradle:3.5.3") 17 | } 18 | } 19 | } 20 | 21 | apply plugin: 'com.android.library' 22 | 23 | android { 24 | compileSdkVersion safeExtGet('compileSdkVersion', 28) 25 | buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3') 26 | 27 | defaultConfig { 28 | minSdkVersion safeExtGet('minSdkVersion', 16) 29 | targetSdkVersion safeExtGet('targetSdkVersion', 28) 30 | versionCode 1 31 | versionName "1.0" 32 | } 33 | lintOptions { 34 | abortOnError false 35 | } 36 | } 37 | 38 | repositories { 39 | google() 40 | mavenCentral() 41 | } 42 | 43 | dependencies { 44 | compile 'com.facebook.react:react-native:+' 45 | } 46 | 47 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/src/main/java/com/codegulp/invokeapp/RNInvokeApp.java: -------------------------------------------------------------------------------- 1 | 2 | package com.codegulp.invokeapp; 3 | 4 | import android.app.ActivityManager; 5 | 6 | import android.content.Context; 7 | import android.content.Intent; 8 | 9 | import android.os.Bundle; 10 | 11 | import android.util.Log; 12 | 13 | import com.facebook.react.bridge.Arguments; 14 | import com.facebook.react.bridge.ReactApplicationContext; 15 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 16 | import com.facebook.react.bridge.ReactMethod; 17 | import com.facebook.react.bridge.ReadableMap; 18 | import com.facebook.react.modules.core.DeviceEventManagerModule; 19 | 20 | import java.util.List; 21 | 22 | public class RNInvokeApp extends ReactContextBaseJavaModule { 23 | 24 | private static ReactApplicationContext reactContext; 25 | public static final String LOG_TAG = "RNInvokeApp"; 26 | private static Bundle bundle = null; 27 | 28 | public RNInvokeApp(ReactApplicationContext context) { 29 | super(context); 30 | reactContext = context; 31 | } 32 | 33 | @Override 34 | public String getName() { 35 | return "ReactNativeInvokeApp"; 36 | } 37 | 38 | @ReactMethod 39 | public void invokeApp(ReadableMap params) { 40 | ReadableMap data = params.hasKey("data") ? params.getMap("data") : null; 41 | 42 | if (data != null) { 43 | bundle = Arguments.toBundle(data); 44 | } 45 | 46 | String packageName = reactContext.getPackageName(); 47 | Intent launchIntent = reactContext.getPackageManager().getLaunchIntentForPackage(packageName); 48 | String className = launchIntent.getComponent().getClassName(); 49 | 50 | try { 51 | Class activityClass = Class.forName(className); 52 | Intent activityIntent = new Intent(reactContext, activityClass); 53 | 54 | activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 55 | reactContext.startActivity(activityIntent); 56 | } catch(Exception e) { 57 | Log.e(LOG_TAG, "Class not found", e); 58 | return; 59 | } 60 | 61 | if (isAppOnForeground(reactContext)) { 62 | sendEvent(); 63 | } 64 | } 65 | 66 | public static void sendEvent() { 67 | if (bundle != null) { 68 | reactContext 69 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 70 | .emit("appInvoked", Arguments.fromBundle(bundle)); 71 | bundle = null; 72 | } 73 | } 74 | 75 | private boolean isAppOnForeground(ReactApplicationContext context) { 76 | /** 77 | * We need to check if app is in foreground otherwise the app will crash. 78 | * http://stackoverflow.com/questions/8489993/check-android-application-is-in-foreground-or-not 79 | **/ 80 | ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 81 | List appProcesses = activityManager.getRunningAppProcesses(); 82 | if (appProcesses == null) { 83 | return false; 84 | } 85 | final String packageName = context.getPackageName(); 86 | for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) { 87 | if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND 88 | && appProcess.processName.equals(packageName)) { 89 | return true; 90 | } 91 | } 92 | return false; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /android/src/main/java/com/codegulp/invokeapp/ReactNativeInvokeAppPackage.java: -------------------------------------------------------------------------------- 1 | 2 | package com.codegulp.invokeapp; 3 | 4 | import java.util.Arrays; 5 | import java.util.Collections; 6 | import java.util.List; 7 | 8 | import com.facebook.react.ReactPackage; 9 | import com.facebook.react.bridge.NativeModule; 10 | import com.facebook.react.bridge.ReactApplicationContext; 11 | import com.facebook.react.uimanager.ViewManager; 12 | import com.facebook.react.bridge.JavaScriptModule; 13 | 14 | public class ReactNativeInvokeAppPackage implements ReactPackage { 15 | @Override 16 | public List createNativeModules(ReactApplicationContext reactContext) { 17 | return Arrays.asList(new RNInvokeApp(reactContext)); 18 | } 19 | 20 | // Deprecated from RN 0.47 21 | public List> createJSModules() { 22 | return Collections.emptyList(); 23 | } 24 | 25 | @Override 26 | public List createViewManagers(ReactApplicationContext reactContext) { 27 | return Collections.emptyList(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | import { NativeModules } from 'react-native'; 3 | 4 | const { ReactNativeInvokeApp } = NativeModules; 5 | 6 | export default (data = {}) => { 7 | ReactNativeInvokeApp.invokeApp(typeof data !== 'object' ? {} : data); 8 | }; 9 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-invoke-app", 3 | "version": "1.0.6", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-invoke-app", 3 | "description": "Bring React Native App to foreground from Headless JS", 4 | "version": "1.0.6", 5 | "license": "MIT", 6 | "main": "index.js", 7 | "author": { 8 | "name": "Ganapathy S", 9 | "email": "vignesh@syncwithtech.org", 10 | "url": "https://www.syncwithtech.org" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/vicke4/react-native-invoke-app.git" 15 | }, 16 | "keywords": [ 17 | "react-native", 18 | "headless js", 19 | "bring app to foreground", 20 | "invoke app" 21 | ], 22 | "bugs": { 23 | "url": "https://github.com/vicke4/react-native-invoke-app/issues", 24 | "email": "vignesh@syncwithtech.org" 25 | }, 26 | "peerDependencies": { 27 | "react-native": "^0.41.2" 28 | } 29 | } 30 | --------------------------------------------------------------------------------