From b4f1f8823058ab68a24032afd43b9df0f6b5d1a8 Mon Sep 17 00:00:00 2001 From: Solimando Damien Date: Mon, 23 Jul 2018 08:37:50 +0200 Subject: [PATCH 1/6] Don't overwrite pre-existing native handlers on iOS --- ios/ReactNativeExceptionHandler.m | 51 +++++++++++++++++++------------ 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/ios/ReactNativeExceptionHandler.m b/ios/ReactNativeExceptionHandler.m index e8bae91..7cb0a71 100644 --- a/ios/ReactNativeExceptionHandler.m +++ b/ios/ReactNativeExceptionHandler.m @@ -28,24 +28,29 @@ - (dispatch_queue_t)methodQueue //variable to hold the custom error handler passed while customizing native handler void (^nativeErrorCallbackBlock)(NSException *exception, NSString *readeableException); +// variable to hold the previously defined error handler +NSUncaughtExceptionHandler* previousNativeErrorCallbackBlock; + +BOOL callPreviousNativeErrorCallbackBlock = false; + //variable to hold the js error handler when setting up the error handler in RN. void (^jsErrorCallbackBlock)(NSException *exception, NSString *readeableException); //variable that holds the default native error handler void (^defaultNativeErrorCallbackBlock)(NSException *exception, NSString *readeableException) = ^(NSException *exception, NSString *readeableException){ - + UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Unexpected error occured" message:[NSString stringWithFormat:@"%@\n%@", @"Apologies..The app will close now \nPlease restart the app\n", readeableException] preferredStyle:UIAlertControllerStyleAlert]; - + UIApplication* app = [UIApplication sharedApplication]; UIViewController * rootViewController = app.delegate.window.rootViewController; [rootViewController presentViewController:alert animated:YES completion:nil]; - + [NSTimer scheduledTimerWithTimeInterval:5.0 target:[ReactNativeExceptionHandler class] selector:@selector(releaseExceptionHold) @@ -53,7 +58,6 @@ - (dispatch_queue_t)methodQueue repeats:NO]; }; - // ==================================== // REACT NATIVE MODULE EXPOSED METHODS // ==================================== @@ -61,12 +65,15 @@ - (dispatch_queue_t)methodQueue RCT_EXPORT_MODULE(); // METHOD TO INITIALIZE THE EXCEPTION HANDLER AND SET THE JS CALLBACK BLOCK -RCT_EXPORT_METHOD(setHandlerforNativeException:(RCTResponseSenderBlock)callback) +RCT_EXPORT_METHOD(setHandlerforNativeException:(BOOL)callPreviouslyDefinedHandler withCallback: (RCTResponseSenderBlock)callback) { jsErrorCallbackBlock = ^(NSException *exception, NSString *readeableException){ callback(@[readeableException]); }; - + + previousNativeErrorCallbackBlock = NSGetUncaughtExceptionHandler(); + callPreviousNativeErrorCallbackBlock = callPreviouslyDefinedHandler; + NSSetUncaughtExceptionHandler(&HandleException); signal(SIGABRT, SignalHandler); signal(SIGILL, SignalHandler); @@ -105,14 +112,19 @@ - (void)handleException:(NSException *)exception [exception reason], [[exception userInfo] objectForKey:RNUncaughtExceptionHandlerAddressesKey]]; dismissApp = false; - + + + if (callPreviousNativeErrorCallbackBlock && previousNativeErrorCallbackBlock) { + previousNativeErrorCallbackBlock(exception); + } + if(nativeErrorCallbackBlock != nil){ nativeErrorCallbackBlock(exception,readeableError); }else{ defaultNativeErrorCallbackBlock(exception,readeableError); } jsErrorCallbackBlock(exception,readeableError); - + CFRunLoopRef runLoop = CFRunLoopGetCurrent(); CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop); while (!dismissApp) @@ -127,9 +139,9 @@ - (void)handleException:(NSException *)exception i++; } } - + CFRelease(allModes); - + NSSetUncaughtExceptionHandler(NULL); signal(SIGABRT, SIG_DFL); signal(SIGILL, SIG_DFL); @@ -137,9 +149,9 @@ - (void)handleException:(NSException *)exception signal(SIGFPE, SIG_DFL); signal(SIGBUS, SIG_DFL); signal(SIGPIPE, SIG_DFL); - + kill(getpid(), [[[exception userInfo] objectForKey:RNUncaughtExceptionHandlerSignalKey] intValue]); - + } @@ -154,14 +166,14 @@ void HandleException(NSException *exception) { return; } - + NSArray *callStack = [ReactNativeExceptionHandler backtrace]; NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]]; [userInfo setObject:callStack forKey:RNUncaughtExceptionHandlerAddressesKey]; - + [[[ReactNativeExceptionHandler alloc] init] performSelectorOnMainThread:@selector(handleException:) withObject: @@ -179,17 +191,17 @@ void SignalHandler(int signal) { return; } - + NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:RNUncaughtExceptionHandlerSignalKey]; - + NSArray *callStack = [ReactNativeExceptionHandler backtrace]; [userInfo setObject:callStack forKey:RNUncaughtExceptionHandlerAddressesKey]; - + [[[ReactNativeExceptionHandler alloc] init] performSelectorOnMainThread:@selector(handleException:) withObject: @@ -216,7 +228,7 @@ + (NSArray *)backtrace void* callstack[128]; int frames = backtrace(callstack, 128); char **strs = backtrace_symbols(callstack, frames); - + int i; NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames]; for ( @@ -228,8 +240,9 @@ + (NSArray *)backtrace [backtrace addObject:[NSString stringWithUTF8String:strs[i]]]; } free(strs); - + return backtrace; } @end + From 56914cf24f0c0f827c6f1d710acb4fdd18dc6b96 Mon Sep 17 00:00:00 2001 From: Solimando Damien Date: Mon, 23 Jul 2018 08:55:44 +0200 Subject: [PATCH 2/6] Allow to define a custom Native handler without displaying Activity --- .../NativeExceptionHandlerIfc.java | 5 + .../ReactNativeExceptionHandlerModule.java | 114 ++++++++++-------- 2 files changed, 70 insertions(+), 49 deletions(-) create mode 100644 android/src/main/java/com/masteratul/exceptionhandler/NativeExceptionHandlerIfc.java diff --git a/android/src/main/java/com/masteratul/exceptionhandler/NativeExceptionHandlerIfc.java b/android/src/main/java/com/masteratul/exceptionhandler/NativeExceptionHandlerIfc.java new file mode 100644 index 0000000..babb49f --- /dev/null +++ b/android/src/main/java/com/masteratul/exceptionhandler/NativeExceptionHandlerIfc.java @@ -0,0 +1,5 @@ +package com.masteratul.exceptionhandler; + +public interface NativeExceptionHandlerIfc { + void handleNativeException(Thread thread, Throwable throwable); +} diff --git a/android/src/main/java/com/masteratul/exceptionhandler/ReactNativeExceptionHandlerModule.java b/android/src/main/java/com/masteratul/exceptionhandler/ReactNativeExceptionHandlerModule.java index eba89dc..ffe47e7 100644 --- a/android/src/main/java/com/masteratul/exceptionhandler/ReactNativeExceptionHandlerModule.java +++ b/android/src/main/java/com/masteratul/exceptionhandler/ReactNativeExceptionHandlerModule.java @@ -1,67 +1,83 @@ package com.masteratul.exceptionhandler; + import android.app.Activity; import android.content.Intent; import android.util.Log; +import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.Callback; public class ReactNativeExceptionHandlerModule extends ReactContextBaseJavaModule { - private ReactApplicationContext reactContext; + private ReactApplicationContext reactContext; private Activity activity; private static Class errorIntentTargetClass = DefaultErrorScreen.class; + private static NativeExceptionHandlerIfc nativeExceptionHandler; private Callback callbackHolder; private Thread.UncaughtExceptionHandler originalHandler; public ReactNativeExceptionHandlerModule(ReactApplicationContext reactContext) { - super(reactContext); - this.reactContext = reactContext; - } - - @Override - public String getName() { - return "ReactNativeExceptionHandler"; - } - - - @ReactMethod - public void setHandlerforNativeException(final boolean forceToQuit, Callback customHandler){ - callbackHolder = customHandler; - originalHandler = Thread.getDefaultUncaughtExceptionHandler(); - - Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread thread, Throwable throwable) { - activity = getCurrentActivity(); - String stackTraceString = Log.getStackTraceString(throwable); - callbackHolder.invoke(stackTraceString); - Log.d("ERROR",stackTraceString); - - - Intent i = new Intent(); - i.setClass(activity, errorIntentTargetClass); - i.putExtra("stack_trace_string",stackTraceString); - i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - activity.startActivity(i); - activity.finish(); - - if (originalHandler != null) { - originalHandler.uncaughtException(thread, throwable); - } - - if (forceToQuit) { - System.exit(0); - } - } - }); - } - - public static void replaceErrorScreenActivityClass(Class errorScreenActivityClass){ - errorIntentTargetClass = errorScreenActivityClass; - } -} + super(reactContext); + this.reactContext = reactContext; + } + + @Override + public String getName() { + return "ReactNativeExceptionHandler"; + } + + + @ReactMethod + public void setHandlerforNativeException( + final boolean executeOriginalUncaughtExceptionHandler, + final boolean forceToQuit, + Callback customHandler) { + + callbackHolder = customHandler; + originalHandler = Thread.getDefaultUncaughtExceptionHandler(); + + Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + + @Override + public void uncaughtException(Thread thread, Throwable throwable) { + + String stackTraceString = Log.getStackTraceString(throwable); + callbackHolder.invoke(stackTraceString); + + if (executeOriginalUncaughtExceptionHandler && originalHandler != null) { + originalHandler.uncaughtException(thread, throwable); + } + + if (nativeExceptionHandler != null) { + nativeExceptionHandler.handleNativeException(thread, throwable); + } else { + activity = getCurrentActivity(); + + Intent i = new Intent(); + i.setClass(activity, errorIntentTargetClass); + i.putExtra("stack_trace_string",stackTraceString); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + activity.startActivity(i); + activity.finish(); + + if (forceToQuit) { + System.exit(0); + } + + } + } + }); + } + + public static void replaceErrorScreenActivityClass(Class errorScreenActivityClass){ + errorIntentTargetClass = errorScreenActivityClass; + } + + public static void setNativeExceptionHandler(NativeExceptionHandlerIfc nativeExceptionHandler) { + ReactNativeExceptionHandlerModule.nativeExceptionHandler = nativeExceptionHandler; + } +} \ No newline at end of file From 86dcd5a8b26574586da3cf01231b80a5aef1e2c8 Mon Sep 17 00:00:00 2001 From: Solimando Damien Date: Mon, 23 Jul 2018 08:56:09 +0200 Subject: [PATCH 3/6] make the execution of the previously defined handler optional --- index.js | 61 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/index.js b/index.js index e917cc0..1eeb236 100644 --- a/index.js +++ b/index.js @@ -1,14 +1,23 @@ +import { NativeModules, Platform } from "react-native"; -import {NativeModules, Platform} from 'react-native'; +const { ReactNativeExceptionHandler } = NativeModules; -const {ReactNativeExceptionHandler} = NativeModules; +const noop = () => { }; -const noop = () => {}; - -export const setJSExceptionHandler = (customHandler = noop, allowedInDevMode = false) => { - if ((typeof allowedInDevMode !== 'boolean') || (typeof customHandler !== 'function')) { - console.log('setJSExceptionHandler is called with wrong argument types.. first argument should be callback function and second argument is optional should be a boolean'); - console.log('Not setting the JS handler .. please fix setJSExceptionHandler call'); +export const setJSExceptionHandler = ( + customHandler = noop, + allowedInDevMode = false +) => { + if ( + typeof allowedInDevMode !== "boolean" || + typeof customHandler !== "function" + ) { + console.log( + "setJSExceptionHandler is called with wrong argument types.. first argument should be callback function and second argument is optional should be a boolean" + ); + console.log( + "Not setting the JS handler .. please fix setJSExceptionHandler call" + ); return; } const allowed = allowedInDevMode ? true : !__DEV__; @@ -16,22 +25,42 @@ export const setJSExceptionHandler = (customHandler = noop, allowedInDevMode = f global.ErrorUtils.setGlobalHandler(customHandler); console.error = (message, error) => global.ErrorUtils.reportError(error); // sending console.error so that it can be caught } else { - console.log('Skipping setJSExceptionHandler: Reason: In DEV mode and allowedInDevMode = false'); + console.log( + "Skipping setJSExceptionHandler: Reason: In DEV mode and allowedInDevMode = false" + ); } }; export const getJSExceptionHandler = () => global.ErrorUtils.getGlobalHandler(); -export const setNativeExceptionHandler = (customErrorHandler = noop, forceApplicationToQuit = true) => { - if ((typeof customErrorHandler !== 'function') || (typeof forceApplicationToQuit !== 'boolean')) { - console.log('setNativeExceptionHandler is called with wrong argument types.. first argument should be callback function and second argument is optional should be a boolean'); - console.log('Not setting the native handler .. please fix setNativeExceptionHandler call'); +export const setNativeExceptionHandler = ( + customErrorHandler = noop, + forceApplicationToQuit = true, + executeDefaultHandler = false +) => { + if ( + typeof customErrorHandler !== "function" || + typeof forceApplicationToQuit !== "boolean" + ) { + console.log( + "setNativeExceptionHandler is called with wrong argument types.. first argument should be callback function and second argument is optional should be a boolean" + ); + console.log( + "Not setting the native handler .. please fix setNativeExceptionHandler call" + ); return; } - if (Platform.OS === 'ios') { - ReactNativeExceptionHandler.setHandlerforNativeException(customErrorHandler); + if (Platform.OS === "ios") { + ReactNativeExceptionHandler.setHandlerforNativeException( + executeDefaultHandler, + customErrorHandler + ); } else { - ReactNativeExceptionHandler.setHandlerforNativeException(forceApplicationToQuit, customErrorHandler); + ReactNativeExceptionHandler.setHandlerforNativeException( + executeDefaultHandler, + forceApplicationToQuit, + customErrorHandler + ); } }; From 750b8ebc1a58f983d7c947ae7eb5a6b8ba21415f Mon Sep 17 00:00:00 2001 From: Solimando Damien Date: Mon, 23 Jul 2018 14:53:37 +0200 Subject: [PATCH 4/6] Fix some regressions --- .../NativeExceptionHandlerIfc.java | 2 +- .../ReactNativeExceptionHandlerModule.java | 89 +++++++++---------- 2 files changed, 45 insertions(+), 46 deletions(-) diff --git a/android/src/main/java/com/masteratul/exceptionhandler/NativeExceptionHandlerIfc.java b/android/src/main/java/com/masteratul/exceptionhandler/NativeExceptionHandlerIfc.java index babb49f..bdcd82e 100644 --- a/android/src/main/java/com/masteratul/exceptionhandler/NativeExceptionHandlerIfc.java +++ b/android/src/main/java/com/masteratul/exceptionhandler/NativeExceptionHandlerIfc.java @@ -1,5 +1,5 @@ package com.masteratul.exceptionhandler; public interface NativeExceptionHandlerIfc { - void handleNativeException(Thread thread, Throwable throwable); + void handleNativeException(Thread thread, Throwable throwable, Thread.UncaughtExceptionHandler originalHandler); } diff --git a/android/src/main/java/com/masteratul/exceptionhandler/ReactNativeExceptionHandlerModule.java b/android/src/main/java/com/masteratul/exceptionhandler/ReactNativeExceptionHandlerModule.java index ffe47e7..00352d3 100644 --- a/android/src/main/java/com/masteratul/exceptionhandler/ReactNativeExceptionHandlerModule.java +++ b/android/src/main/java/com/masteratul/exceptionhandler/ReactNativeExceptionHandlerModule.java @@ -12,7 +12,7 @@ public class ReactNativeExceptionHandlerModule extends ReactContextBaseJavaModule { - private ReactApplicationContext reactContext; + private ReactApplicationContext reactContext; private Activity activity; private static Class errorIntentTargetClass = DefaultErrorScreen.class; private static NativeExceptionHandlerIfc nativeExceptionHandler; @@ -20,64 +20,63 @@ public class ReactNativeExceptionHandlerModule extends ReactContextBaseJavaModul private Thread.UncaughtExceptionHandler originalHandler; public ReactNativeExceptionHandlerModule(ReactApplicationContext reactContext) { - super(reactContext); - this.reactContext = reactContext; - } + super(reactContext); + this.reactContext = reactContext; + } - @Override - public String getName() { - return "ReactNativeExceptionHandler"; - } + @Override + public String getName() { + return "ReactNativeExceptionHandler"; + } - @ReactMethod - public void setHandlerforNativeException( - final boolean executeOriginalUncaughtExceptionHandler, - final boolean forceToQuit, - Callback customHandler) { + @ReactMethod + public void setHandlerforNativeException( + final boolean executeOriginalUncaughtExceptionHandler, + final boolean forceToQuit, + Callback customHandler) { - callbackHolder = customHandler; - originalHandler = Thread.getDefaultUncaughtExceptionHandler(); + callbackHolder = customHandler; + originalHandler = Thread.getDefaultUncaughtExceptionHandler(); - Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread thread, Throwable throwable) { + @Override + public void uncaughtException(Thread thread, Throwable throwable) { - String stackTraceString = Log.getStackTraceString(throwable); - callbackHolder.invoke(stackTraceString); + String stackTraceString = Log.getStackTraceString(throwable); + callbackHolder.invoke(stackTraceString); - if (executeOriginalUncaughtExceptionHandler && originalHandler != null) { - originalHandler.uncaughtException(thread, throwable); - } + if (nativeExceptionHandler != null) { + nativeExceptionHandler.handleNativeException(thread, throwable, originalHandler); + } else { + activity = getCurrentActivity(); - if (nativeExceptionHandler != null) { - nativeExceptionHandler.handleNativeException(thread, throwable); - } else { - activity = getCurrentActivity(); + Intent i = new Intent(); + i.setClass(activity, errorIntentTargetClass); + i.putExtra("stack_trace_string",stackTraceString); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - Intent i = new Intent(); - i.setClass(activity, errorIntentTargetClass); - i.putExtra("stack_trace_string",stackTraceString); - i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + activity.startActivity(i); + activity.finish(); - activity.startActivity(i); - activity.finish(); + if (executeOriginalUncaughtExceptionHandler && originalHandler != null) { + originalHandler.uncaughtException(thread, throwable); + } - if (forceToQuit) { - System.exit(0); - } + if (forceToQuit) { + System.exit(0); + } + } + } + }); + } - } - } - }); - } - - public static void replaceErrorScreenActivityClass(Class errorScreenActivityClass){ - errorIntentTargetClass = errorScreenActivityClass; - } + public static void replaceErrorScreenActivityClass(Class errorScreenActivityClass){ + errorIntentTargetClass = errorScreenActivityClass; + } public static void setNativeExceptionHandler(NativeExceptionHandlerIfc nativeExceptionHandler) { ReactNativeExceptionHandlerModule.nativeExceptionHandler = nativeExceptionHandler; } -} \ No newline at end of file +} From 703a98b8cc0ca694428f56932205bdeac69f07bf Mon Sep 17 00:00:00 2001 From: Solimando Damien Date: Wed, 25 Jul 2018 11:27:52 +0200 Subject: [PATCH 5/6] Update README.md --- README.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/README.md b/README.md index 793c371..ac0ad89 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,34 @@ public class MainApplication extends Application implements ReactApplication { ``` +**Modifying Android Native Exception handler** (NATIVE CODE HAS TO BE WRITTEN) *recommended that you do this in android studio* + +- In the `android/app/src/main/java/[...]/MainActivity.java` + +```java +import com.masteratul.exceptionhandler.ReactNativeExceptionHandlerModule; +import com.masteratul.exceptionhandler.NativeExceptionHandlerIfc +... +... +... +public class MainApplication extends Application implements ReactApplication { +... +... + @Override + public void onCreate() { + .... + .... + .... + ReactNativeExceptionHandlerModule.setNativeExceptionHandler(new NativeExceptionHandlerIfc() { + @Override + public void handleNativeException(Thread thread, Throwable throwable, Thread.UncaughtExceptionHandler originalHandler) { + // Put your error handling code here + } + }//This will override the default behaviour of displaying the recover activity. + } + +``` + **Modifying iOS Native Exception handler UI** (NATIVE CODE HAS TO BE WRITTEN) *recommended that you do this in XCode* Unlike Android, in the case of iOS, there is no way to restart the app if it has crashed. Also, during a **Native_Exceptions** the UI becomes quite unstable since the exception occured on the main UI thread. Hence, none of the click or press handlers would work either. @@ -410,6 +438,10 @@ This is specifically occuring when you use [wix library](http://wix.github.io/re setNativeExceptionHandler(nativeErrorCallback, false); ``` +### Previously defined exception handlers are not executed anymore + +A lot of frameworks (especially analytics sdk's) implement global exception handlers. In order to keep these frameworks working while using react-native-exception-hanlder, you can pass a boolean value as third argument to `setNativeExceptionHandler(..., ..., true`) what will trigger the execution of the last global handler registered. + ## CONTRIBUTORS - [Atul R](https://github.com/master-atul) @@ -426,6 +458,7 @@ setNativeExceptionHandler(nativeErrorCallback, false); - [TomMahle](https://github.com/TomMahle) - [Sébastien Krafft](https://github.com/skrafft) - [Mark Friedman](https://github.com/mark-friedman) +- [Damien Solimando](https://github.com/dsolimando) ## TESTING NATIVE EXCEPTIONS/ERRORS From e03a7c4b4a7053023f230f48ecbbcd4e9b316028 Mon Sep 17 00:00:00 2001 From: Atul Date: Fri, 27 Jul 2018 22:43:17 +0530 Subject: [PATCH 6/6] Bumped version and added readme. --- README.md | 55 ++++++++++++++++++++++++++++++++-------------------- index.js | 52 +++++++++++-------------------------------------- package.json | 2 +- 3 files changed, 46 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index e7d3368..57ab44b 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,11 @@ To tackle this we register a global error handler that could be used to for exam **V2 of this module now supports catching Unhandled Native Exceptions also along with the JS Exceptions ✌🏻🍻** There are **NO** breaking changes. So its safe to upgrade from v1 to v2. So there is no reason not to 😉. +**V2.9** +- Adds support for executing previously set error handlers (now this module can work with other analytics modules) +- Adds an improved approach for overwriting native error handlers. +- Thanks @ [Damien Solimando](https://github.com/dsolimando) + **Example** repo can be found here: *[https://github.com/master-atul/react-native-exception-handler-example](https://github.com/master-atul/react-native-exception-handler-example) * @@ -128,7 +133,7 @@ setJSExceptionHandler((error, isFatal) => { // or hit google analytics to track crashes // or hit a custom api to inform the dev team. }); - +//================================================= // ADVANCED use case: const exceptionhandler = (error, isFatal) => { // your error handler function @@ -156,17 +161,21 @@ setNativeExceptionHandler((exceptionString) => { //NOTE: alert or showing any UI change via JS //WILL NOT WORK in case of NATIVE ERRORS. }); - +//==================================================== // ADVANCED use case: const exceptionhandler = (exceptionString) => { // your exception handler code here } -setNativeExceptionHandler(exceptionhandler,forceAppQuit); +setNativeExceptionHandler(exceptionhandler,forceAppQuit,executeDefaultHandler); // - exceptionhandler is the exception handler function // - forceAppQuit is an optional ANDROID specific parameter that defines -// if the app should be force quit on error. default value is true. -// To see usecase check the common issues section. - +// if the app should be force quit on error. default value is true. +// To see usecase check the common issues section. +// - executeDefaultHandler is an optional boolean (both IOS, ANDROID) +// It executes previous exception handlers if set by some other module. +// It will come handy when you use any other crash analytics module along with this one +// Default value is set to false. Set to true if you are using other analytics modules. + ``` It is recommended you set both the handlers. @@ -201,15 +210,15 @@ In Android and iOS you will see something like

-**Modifying Android Native Exception handler UI** (NATIVE CODE HAS TO BE WRITTEN) *recommended that you do this in android studio* +**Modifying Android Native Exception handler (RECOMMENDED APPROACH)** -- Create an Empty Activity in the `android/app/src/main/java/[...]/`. For example lets say CustomErrorDialog.java -- Customize your activity to look and behave however you need it to be. -- In the `android/app/src/main/java/[...]/MainApplication.java` +(NATIVE CODE HAS TO BE WRITTEN) *recommended that you do this in android studio* + +- In the `android/app/src/main/java/[...]/MainActivity.java` ```java import com.masteratul.exceptionhandler.ReactNativeExceptionHandlerModule; -import .YourCustomActivity; //This is your CustomErrorDialog.java +import com.masteratul.exceptionhandler.NativeExceptionHandlerIfc ... ... ... @@ -221,18 +230,27 @@ public class MainApplication extends Application implements ReactApplication { .... .... .... - ReactNativeExceptionHandlerModule.replaceErrorScreenActivityClass(YourCustomActivity.class); //This will replace the native error handler popup with your own custom activity. + ReactNativeExceptionHandlerModule.setNativeExceptionHandler(new NativeExceptionHandlerIfc() { + @Override + public void handleNativeException(Thread thread, Throwable throwable, Thread.UncaughtExceptionHandler originalHandler) { + // Put your error handling code here + } + }//This will override the default behaviour of displaying the recover activity. } ``` -**Modifying Android Native Exception handler** (NATIVE CODE HAS TO BE WRITTEN) *recommended that you do this in android studio* +**Modifying Android Native Exception handler UI (CUSTOM ACTIVITY APPROACH (OLD APPROACH).. LEAVING FOR BACKWARD COMPATIBILITY)** -- In the `android/app/src/main/java/[...]/MainActivity.java` +(NATIVE CODE HAS TO BE WRITTEN) *recommended that you do this in android studio* + +- Create an Empty Activity in the `android/app/src/main/java/[...]/`. For example lets say CustomErrorDialog.java +- Customize your activity to look and behave however you need it to be. +- In the `android/app/src/main/java/[...]/MainApplication.java` ```java import com.masteratul.exceptionhandler.ReactNativeExceptionHandlerModule; -import com.masteratul.exceptionhandler.NativeExceptionHandlerIfc +import .YourCustomActivity; //This is your CustomErrorDialog.java ... ... ... @@ -244,12 +262,7 @@ public class MainApplication extends Application implements ReactApplication { .... .... .... - ReactNativeExceptionHandlerModule.setNativeExceptionHandler(new NativeExceptionHandlerIfc() { - @Override - public void handleNativeException(Thread thread, Throwable throwable, Thread.UncaughtExceptionHandler originalHandler) { - // Put your error handling code here - } - }//This will override the default behaviour of displaying the recover activity. + ReactNativeExceptionHandlerModule.replaceErrorScreenActivityClass(YourCustomActivity.class); //This will replace the native error handler popup with your own custom activity. } ``` diff --git a/index.js b/index.js index 1eeb236..d1b3200 100644 --- a/index.js +++ b/index.js @@ -4,20 +4,10 @@ const { ReactNativeExceptionHandler } = NativeModules; const noop = () => { }; -export const setJSExceptionHandler = ( - customHandler = noop, - allowedInDevMode = false -) => { - if ( - typeof allowedInDevMode !== "boolean" || - typeof customHandler !== "function" - ) { - console.log( - "setJSExceptionHandler is called with wrong argument types.. first argument should be callback function and second argument is optional should be a boolean" - ); - console.log( - "Not setting the JS handler .. please fix setJSExceptionHandler call" - ); +export const setJSExceptionHandler = (customHandler = noop, allowedInDevMode = false) => { + if (typeof allowedInDevMode !== "boolean" || typeof customHandler !== "function"){ + console.log("setJSExceptionHandler is called with wrong argument types.. first argument should be callback function and second argument is optional should be a boolean"); + console.log("Not setting the JS handler .. please fix setJSExceptionHandler call"); return; } const allowed = allowedInDevMode ? true : !__DEV__; @@ -25,42 +15,22 @@ export const setJSExceptionHandler = ( global.ErrorUtils.setGlobalHandler(customHandler); console.error = (message, error) => global.ErrorUtils.reportError(error); // sending console.error so that it can be caught } else { - console.log( - "Skipping setJSExceptionHandler: Reason: In DEV mode and allowedInDevMode = false" - ); + console.log("Skipping setJSExceptionHandler: Reason: In DEV mode and allowedInDevMode = false"); } }; export const getJSExceptionHandler = () => global.ErrorUtils.getGlobalHandler(); -export const setNativeExceptionHandler = ( - customErrorHandler = noop, - forceApplicationToQuit = true, - executeDefaultHandler = false -) => { - if ( - typeof customErrorHandler !== "function" || - typeof forceApplicationToQuit !== "boolean" - ) { - console.log( - "setNativeExceptionHandler is called with wrong argument types.. first argument should be callback function and second argument is optional should be a boolean" - ); - console.log( - "Not setting the native handler .. please fix setNativeExceptionHandler call" - ); +export const setNativeExceptionHandler = (customErrorHandler = noop, forceApplicationToQuit = true, executeDefaultHandler = false) => { + if (typeof customErrorHandler !== "function" || typeof forceApplicationToQuit !== "boolean") { + console.log("setNativeExceptionHandler is called with wrong argument types.. first argument should be callback function and second argument is optional should be a boolean"); + console.log("Not setting the native handler .. please fix setNativeExceptionHandler call"); return; } if (Platform.OS === "ios") { - ReactNativeExceptionHandler.setHandlerforNativeException( - executeDefaultHandler, - customErrorHandler - ); + ReactNativeExceptionHandler.setHandlerforNativeException(executeDefaultHandler, customErrorHandler); } else { - ReactNativeExceptionHandler.setHandlerforNativeException( - executeDefaultHandler, - forceApplicationToQuit, - customErrorHandler - ); + ReactNativeExceptionHandler.setHandlerforNativeException(executeDefaultHandler, forceApplicationToQuit, customErrorHandler); } }; diff --git a/package.json b/package.json index bdd8e77..ce68a48 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-exception-handler", - "version": "2.8.9", + "version": "2.9.0", "description": "A react native module that lets you to register a global error handler that can capture fatal/non fatal uncaught exceptions.", "main": "index.js", "scripts": {