Skip to content

Commit dca5aa6

Browse files
committed
Merge branch 'develop' of github.com:master-atul/react-native-exception-handler into develop
2 parents f7bac35 + f1b1d2f commit dca5aa6

File tree

5 files changed

+139
-44
lines changed

5 files changed

+139
-44
lines changed

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,34 @@ public class MainApplication extends Application implements ReactApplication {
226226

227227
```
228228

229+
**Modifying Android Native Exception handler** (NATIVE CODE HAS TO BE WRITTEN) *recommended that you do this in android studio*
230+
231+
- In the `android/app/src/main/java/[...]/MainActivity.java`
232+
233+
```java
234+
import com.masteratul.exceptionhandler.ReactNativeExceptionHandlerModule;
235+
import com.masteratul.exceptionhandler.NativeExceptionHandlerIfc
236+
...
237+
...
238+
...
239+
public class MainApplication extends Application implements ReactApplication {
240+
...
241+
...
242+
@Override
243+
public void onCreate() {
244+
....
245+
....
246+
....
247+
ReactNativeExceptionHandlerModule.setNativeExceptionHandler(new NativeExceptionHandlerIfc() {
248+
@Override
249+
public void handleNativeException(Thread thread, Throwable throwable, Thread.UncaughtExceptionHandler originalHandler) {
250+
// Put your error handling code here
251+
}
252+
}//This will override the default behaviour of displaying the recover activity.
253+
}
254+
255+
```
256+
229257
**Modifying iOS Native Exception handler UI** (NATIVE CODE HAS TO BE WRITTEN) *recommended that you do this in XCode*
230258

231259
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.
@@ -431,6 +459,10 @@ This is specifically occuring when you use [wix library](http://wix.github.io/re
431459
setNativeExceptionHandler(nativeErrorCallback, false);
432460
```
433461

462+
### Previously defined exception handlers are not executed anymore
463+
464+
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.
465+
434466
435467
## CONTRIBUTORS
436468
- [Atul R](https://github.com/master-atul)
@@ -447,6 +479,7 @@ setNativeExceptionHandler(nativeErrorCallback, false);
447479
- [TomMahle](https://github.com/TomMahle)
448480
- [Sébastien Krafft](https://github.com/skrafft)
449481
- [Mark Friedman](https://github.com/mark-friedman)
482+
- [Damien Solimando](https://github.com/dsolimando)
450483
451484
## TESTING NATIVE EXCEPTIONS/ERRORS
452485
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.masteratul.exceptionhandler;
2+
3+
public interface NativeExceptionHandlerIfc {
4+
void handleNativeException(Thread thread, Throwable throwable, Thread.UncaughtExceptionHandler originalHandler);
5+
}
Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11

22
package com.masteratul.exceptionhandler;
3+
34
import android.app.Activity;
45
import android.content.Intent;
56
import android.util.Log;
67

8+
import com.facebook.react.bridge.Callback;
79
import com.facebook.react.bridge.ReactApplicationContext;
810
import com.facebook.react.bridge.ReactContextBaseJavaModule;
911
import com.facebook.react.bridge.ReactMethod;
10-
import com.facebook.react.bridge.Callback;
1112

1213
public class ReactNativeExceptionHandlerModule extends ReactContextBaseJavaModule {
1314

1415
private ReactApplicationContext reactContext;
1516
private Activity activity;
1617
private static Class errorIntentTargetClass = DefaultErrorScreen.class;
18+
private static NativeExceptionHandlerIfc nativeExceptionHandler;
1719
private Callback callbackHolder;
1820
private Thread.UncaughtExceptionHandler originalHandler;
1921

@@ -29,39 +31,52 @@ public String getName() {
2931

3032

3133
@ReactMethod
32-
public void setHandlerforNativeException(final boolean forceToQuit, Callback customHandler){
34+
public void setHandlerforNativeException(
35+
final boolean executeOriginalUncaughtExceptionHandler,
36+
final boolean forceToQuit,
37+
Callback customHandler) {
38+
3339
callbackHolder = customHandler;
3440
originalHandler = Thread.getDefaultUncaughtExceptionHandler();
3541

3642
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
43+
3744
@Override
3845
public void uncaughtException(Thread thread, Throwable throwable) {
46+
47+
String stackTraceString = Log.getStackTraceString(throwable);
48+
callbackHolder.invoke(stackTraceString);
49+
50+
if (nativeExceptionHandler != null) {
51+
nativeExceptionHandler.handleNativeException(thread, throwable, originalHandler);
52+
} else {
3953
activity = getCurrentActivity();
40-
String stackTraceString = Log.getStackTraceString(throwable);
41-
callbackHolder.invoke(stackTraceString);
42-
Log.d("ERROR",stackTraceString);
4354

44-
4555
Intent i = new Intent();
4656
i.setClass(activity, errorIntentTargetClass);
4757
i.putExtra("stack_trace_string",stackTraceString);
4858
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
49-
59+
5060
activity.startActivity(i);
5161
activity.finish();
5262

53-
if (originalHandler != null) {
63+
if (executeOriginalUncaughtExceptionHandler && originalHandler != null) {
5464
originalHandler.uncaughtException(thread, throwable);
5565
}
5666

5767
if (forceToQuit) {
58-
System.exit(0);
68+
System.exit(0);
5969
}
6070
}
71+
}
6172
});
6273
}
6374

6475
public static void replaceErrorScreenActivityClass(Class errorScreenActivityClass){
6576
errorIntentTargetClass = errorScreenActivityClass;
6677
}
78+
79+
public static void setNativeExceptionHandler(NativeExceptionHandlerIfc nativeExceptionHandler) {
80+
ReactNativeExceptionHandlerModule.nativeExceptionHandler = nativeExceptionHandler;
81+
}
6782
}

index.js

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,66 @@
1+
import { NativeModules, Platform } from "react-native";
12

2-
import {NativeModules, Platform} from 'react-native';
3+
const { ReactNativeExceptionHandler } = NativeModules;
34

4-
const {ReactNativeExceptionHandler} = NativeModules;
5+
const noop = () => { };
56

6-
const noop = () => {};
7-
8-
export const setJSExceptionHandler = (customHandler = noop, allowedInDevMode = false) => {
9-
if ((typeof allowedInDevMode !== 'boolean') || (typeof customHandler !== 'function')) {
10-
console.log('setJSExceptionHandler is called with wrong argument types.. first argument should be callback function and second argument is optional should be a boolean');
11-
console.log('Not setting the JS handler .. please fix setJSExceptionHandler call');
7+
export const setJSExceptionHandler = (
8+
customHandler = noop,
9+
allowedInDevMode = false
10+
) => {
11+
if (
12+
typeof allowedInDevMode !== "boolean" ||
13+
typeof customHandler !== "function"
14+
) {
15+
console.log(
16+
"setJSExceptionHandler is called with wrong argument types.. first argument should be callback function and second argument is optional should be a boolean"
17+
);
18+
console.log(
19+
"Not setting the JS handler .. please fix setJSExceptionHandler call"
20+
);
1221
return;
1322
}
1423
const allowed = allowedInDevMode ? true : !__DEV__;
1524
if (allowed) {
1625
global.ErrorUtils.setGlobalHandler(customHandler);
1726
console.error = (message, error) => global.ErrorUtils.reportError(error); // sending console.error so that it can be caught
1827
} else {
19-
console.log('Skipping setJSExceptionHandler: Reason: In DEV mode and allowedInDevMode = false');
28+
console.log(
29+
"Skipping setJSExceptionHandler: Reason: In DEV mode and allowedInDevMode = false"
30+
);
2031
}
2132
};
2233

2334
export const getJSExceptionHandler = () => global.ErrorUtils.getGlobalHandler();
2435

25-
export const setNativeExceptionHandler = (customErrorHandler = noop, forceApplicationToQuit = true) => {
26-
if ((typeof customErrorHandler !== 'function') || (typeof forceApplicationToQuit !== 'boolean')) {
27-
console.log('setNativeExceptionHandler is called with wrong argument types.. first argument should be callback function and second argument is optional should be a boolean');
28-
console.log('Not setting the native handler .. please fix setNativeExceptionHandler call');
36+
export const setNativeExceptionHandler = (
37+
customErrorHandler = noop,
38+
forceApplicationToQuit = true,
39+
executeDefaultHandler = false
40+
) => {
41+
if (
42+
typeof customErrorHandler !== "function" ||
43+
typeof forceApplicationToQuit !== "boolean"
44+
) {
45+
console.log(
46+
"setNativeExceptionHandler is called with wrong argument types.. first argument should be callback function and second argument is optional should be a boolean"
47+
);
48+
console.log(
49+
"Not setting the native handler .. please fix setNativeExceptionHandler call"
50+
);
2951
return;
3052
}
31-
if (Platform.OS === 'ios') {
32-
ReactNativeExceptionHandler.setHandlerforNativeException(customErrorHandler);
53+
if (Platform.OS === "ios") {
54+
ReactNativeExceptionHandler.setHandlerforNativeException(
55+
executeDefaultHandler,
56+
customErrorHandler
57+
);
3358
} else {
34-
ReactNativeExceptionHandler.setHandlerforNativeException(forceApplicationToQuit, customErrorHandler);
59+
ReactNativeExceptionHandler.setHandlerforNativeException(
60+
executeDefaultHandler,
61+
forceApplicationToQuit,
62+
customErrorHandler
63+
);
3564
}
3665
};
3766

ios/ReactNativeExceptionHandler.m

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,45 +28,52 @@ - (dispatch_queue_t)methodQueue
2828
//variable to hold the custom error handler passed while customizing native handler
2929
void (^nativeErrorCallbackBlock)(NSException *exception, NSString *readeableException);
3030

31+
// variable to hold the previously defined error handler
32+
NSUncaughtExceptionHandler* previousNativeErrorCallbackBlock;
33+
34+
BOOL callPreviousNativeErrorCallbackBlock = false;
35+
3136
//variable to hold the js error handler when setting up the error handler in RN.
3237
void (^jsErrorCallbackBlock)(NSException *exception, NSString *readeableException);
3338

3439
//variable that holds the default native error handler
3540
void (^defaultNativeErrorCallbackBlock)(NSException *exception, NSString *readeableException) =
3641
^(NSException *exception, NSString *readeableException){
37-
42+
3843
UIAlertController* alert = [UIAlertController
3944
alertControllerWithTitle:@"Unexpected error occured"
4045
message:[NSString stringWithFormat:@"%@\n%@",
4146
@"Apologies..The app will close now \nPlease restart the app\n",
4247
readeableException]
4348
preferredStyle:UIAlertControllerStyleAlert];
44-
49+
4550
UIApplication* app = [UIApplication sharedApplication];
4651
UIViewController * rootViewController = app.delegate.window.rootViewController;
4752
[rootViewController presentViewController:alert animated:YES completion:nil];
48-
53+
4954
[NSTimer scheduledTimerWithTimeInterval:5.0
5055
target:[ReactNativeExceptionHandler class]
5156
selector:@selector(releaseExceptionHold)
5257
userInfo:nil
5358
repeats:NO];
5459
};
5560

56-
5761
// ====================================
5862
// REACT NATIVE MODULE EXPOSED METHODS
5963
// ====================================
6064

6165
RCT_EXPORT_MODULE();
6266

6367
// METHOD TO INITIALIZE THE EXCEPTION HANDLER AND SET THE JS CALLBACK BLOCK
64-
RCT_EXPORT_METHOD(setHandlerforNativeException:(RCTResponseSenderBlock)callback)
68+
RCT_EXPORT_METHOD(setHandlerforNativeException:(BOOL)callPreviouslyDefinedHandler withCallback: (RCTResponseSenderBlock)callback)
6569
{
6670
jsErrorCallbackBlock = ^(NSException *exception, NSString *readeableException){
6771
callback(@[readeableException]);
6872
};
69-
73+
74+
previousNativeErrorCallbackBlock = NSGetUncaughtExceptionHandler();
75+
callPreviousNativeErrorCallbackBlock = callPreviouslyDefinedHandler;
76+
7077
NSSetUncaughtExceptionHandler(&HandleException);
7178
signal(SIGABRT, SignalHandler);
7279
signal(SIGILL, SignalHandler);
@@ -105,14 +112,19 @@ - (void)handleException:(NSException *)exception
105112
[exception reason],
106113
[[exception userInfo] objectForKey:RNUncaughtExceptionHandlerAddressesKey]];
107114
dismissApp = false;
108-
115+
116+
117+
if (callPreviousNativeErrorCallbackBlock && previousNativeErrorCallbackBlock) {
118+
previousNativeErrorCallbackBlock(exception);
119+
}
120+
109121
if(nativeErrorCallbackBlock != nil){
110122
nativeErrorCallbackBlock(exception,readeableError);
111123
}else{
112124
defaultNativeErrorCallbackBlock(exception,readeableError);
113125
}
114126
jsErrorCallbackBlock(exception,readeableError);
115-
127+
116128
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
117129
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
118130
while (!dismissApp)
@@ -127,19 +139,19 @@ - (void)handleException:(NSException *)exception
127139
i++;
128140
}
129141
}
130-
142+
131143
CFRelease(allModes);
132-
144+
133145
NSSetUncaughtExceptionHandler(NULL);
134146
signal(SIGABRT, SIG_DFL);
135147
signal(SIGILL, SIG_DFL);
136148
signal(SIGSEGV, SIG_DFL);
137149
signal(SIGFPE, SIG_DFL);
138150
signal(SIGBUS, SIG_DFL);
139151
signal(SIGPIPE, SIG_DFL);
140-
152+
141153
kill(getpid(), [[[exception userInfo] objectForKey:RNUncaughtExceptionHandlerSignalKey] intValue]);
142-
154+
143155
}
144156

145157

@@ -154,14 +166,14 @@ void HandleException(NSException *exception)
154166
{
155167
return;
156168
}
157-
169+
158170
NSArray *callStack = [ReactNativeExceptionHandler backtrace];
159171
NSMutableDictionary *userInfo =
160172
[NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
161173
[userInfo
162174
setObject:callStack
163175
forKey:RNUncaughtExceptionHandlerAddressesKey];
164-
176+
165177
[[[ReactNativeExceptionHandler alloc] init]
166178
performSelectorOnMainThread:@selector(handleException:)
167179
withObject:
@@ -179,17 +191,17 @@ void SignalHandler(int signal)
179191
{
180192
return;
181193
}
182-
194+
183195
NSMutableDictionary *userInfo =
184196
[NSMutableDictionary
185197
dictionaryWithObject:[NSNumber numberWithInt:signal]
186198
forKey:RNUncaughtExceptionHandlerSignalKey];
187-
199+
188200
NSArray *callStack = [ReactNativeExceptionHandler backtrace];
189201
[userInfo
190202
setObject:callStack
191203
forKey:RNUncaughtExceptionHandlerAddressesKey];
192-
204+
193205
[[[ReactNativeExceptionHandler alloc] init]
194206
performSelectorOnMainThread:@selector(handleException:)
195207
withObject:
@@ -216,7 +228,7 @@ + (NSArray *)backtrace
216228
void* callstack[128];
217229
int frames = backtrace(callstack, 128);
218230
char **strs = backtrace_symbols(callstack, frames);
219-
231+
220232
int i;
221233
NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
222234
for (
@@ -228,8 +240,9 @@ + (NSArray *)backtrace
228240
[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
229241
}
230242
free(strs);
231-
243+
232244
return backtrace;
233245
}
234246

235247
@end
248+

0 commit comments

Comments
 (0)