Repository files navigation

RxJava2Debug Tweet

LicenseMavenJcenterArsenalMethods

A library to make StackTraces involving RxJava2 more meaningful (they will always point to your code!).

Rationale

If you use RxJava2, you know the pain of debugging exceptions generated somwhere across the stream, in another thread, in a nested stream, ... The StackTrace provided by RxJava2 will tell you almost nothing. Even more, if you are using systems like Crashlytics to collect reports for and Android app, most RxJava-related crashes will be reported as a single error instance (omg, no way to fix that).

This library was created from the need to debug such situations when they happen in Android apps; even more, I needed to have a clear and precise report in Crashlytics for each distinct exception.

  1. If you handle exceptions generated in RxJava2 streams, you can query RxJava2Debug to obtain an extended StackTrace pointing to the exact line of code that created the issue.

  2. If you let exceptions generated in RxJava2 streams crash your app, RxJava2Debug will automatically extend the default StackTrace.

  3. If you let exceptions generated in RxJava2 streams crash your app and you configure RxJava2Debug with your package-names, RxJava2Debug will make sure you get unique reports for each issue (to Crashlytics, or whichever reporting system you use).

Installation

Using Maven Central :

repositories {
mavenCentral()
}
dependencies {
compile 'com.akaita.java:rxjava2-debug:1.4.0'
}

Using JCenter:

repositories {
jcenter()
}
dependencies {
compile 'com.akaita.java:rxjava2-debug:1.4.0'
}

Compatibility

RxJava2Debug 1.3.0+: RxJava 2.2.1+

RxJava2Debug 1.2.2: RxJava 2.1.0 to 2.2.0

Usage

Just enable RxJava2Debug as soon as possible. In Android, for example:

publicclassMyApplicationextendsApplication {
@OverridepublicvoidonCreate() {
super.onCreate();
Fabric.with(this, newCrashlytics());
// Enable RxJava assembly stack collection, to make RxJava crash reports clear and unique// Make sure this is called AFTER setting up any Crash reporting mechanism as CrashlyticsRxJava2Debug.enableRxJava2AssemblyTracking(newString[]{"com.example.myapp", "com.example.mylibrary"});
}
}

This will:

  • Enhance stack traces of all RxJava2-related crashes making sure they contain a reference to the method that generated the first event in the Rx pipeline
  • Make sure that Stack Traces contain a reference to your code (some class in com.example.myapp or com.example.mylibrary)
  • Make sure that crash reports in Crashlytics are actually different for each pipeline (avoid bundling every RXJava error into one reports)

You will now also be able to obtain an enhanced Stack Trace even when you implement onError (really, you should implement it):

responseSubject
.subscribe(
responseObservable -> handleResponse(responseObservable),
throwable -> RxJava2Debug.getEnhancedStackTrace(throwable)
);
Without RxJava2DebugWith RxJava2Debug
raw stack traceenhanced stack trace
raw crash reportenhanced crash report

API

Start collecting information about RxJava's execution to provide a more meaningful StackTrace in case of crash
Beware: Any crash-reporting handler should be set up before calling this method

voidenableRxJava2AssemblyTracking()

Start collecting filtered information about RxJava's execution to provide a more meaningful StackTrace in case of crash
Beware: Any crash-reporting handler should be set up before calling this method

voidenableRxJava2AssemblyTracking(@NullableString[] basePackageNames)

Disable the collection of more information about RxJava's execution
Information collected before calling this method will still be reported

voiddisableRxJava2AssemblyTracking()

Obtain a copy of the original Throwable with an extended StackTrace

@NullableThrowablegetEnhancedStackTrace(ThrowableoriginalException)

Features

StackTrace generation

Regardless of the thread a crash happens in, a nice StackTrace referring to the origin of the RxJava2 pipeline will be added to the causes of the crash.

Config:

RxJava2Debug.enableRxJava2AssemblyTracking();

Example (pointing to my own faulty code in com.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)):

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 22538java.lang.Throwable: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
athu.akarnokd.rxjava2.debug.ObservableOnAssembly$OnAssemblyObserver.onNext(ObservableOnAssembly.java:55)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
athu.akarnokd.rxjava2.debug.ObservableOnAssembly$OnAssemblyObserver.onNext(ObservableOnAssembly.java:55)
atio.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
atio.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:35)
atio.reactivex.Observable.subscribe(Observable.java:10838)
athu.akarnokd.rxjava2.debug.ObservableOnAssemblyScalarCallable.subscribeActual(ObservableOnAssemblyScalarCallable.java:41)
atio.reactivex.Observable.subscribe(Observable.java:10838)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
atio.reactivex.Scheduler$DisposeTask.run(Scheduler.java:452)
atio.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:61)
atio.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:52)
atjava.util.concurrent.FutureTask.run(FutureTask.java:237)
atjava.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
atjava.lang.Thread.run(Thread.java:761)
Causedby: causedbyjava.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atcom.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)
atandroid.app.Instrumentation.callActivityOnResume(Instrumentation.java:1269)
atandroid.app.Activity.performResume(Activity.java:6766)
atandroid.app.ActivityThread.performResumeActivity(ActivityThread.java:3377)
atandroid.app.ActivityThread.handleResumeActivity(ActivityThread.java:3440)
atandroid.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2713)
atandroid.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
atandroid.os.Handler.dispatchMessage(Handler.java:102)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)

Instead of the obscure:

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 27300io.reactivex.exceptions.OnErrorNotImplementedException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:704)
atio.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:701)
atio.reactivex.internal.observers.LambdaObserver.onError(LambdaObserver.java:74)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.checkTerminated(ObservableObserveOn.java:276)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:172)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:252)
atio.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:109)
atandroid.os.Handler.handleCallback(Handler.java:751)
atandroid.os.Handler.dispatchMessage(Handler.java:95)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atjava.lang.reflect.Method.invoke(NativeMethod)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
Causedby: java.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
atio.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
atio.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:35)
atio.reactivex.Observable.subscribe(Observable.java:10838)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
atio.reactivex.Scheduler$DisposeTask.run(Scheduler.java:452)
atio.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:61)
atio.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:52)
atjava.util.concurrent.FutureTask.run(FutureTask.java:237)
atjava.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
atjava.lang.Thread.run(Thread.java:761)

StackTrace filtering

Tell RxJava2Debug which packages names you want to highlight, and RxJava2Debug will make sure they appear as the top of the StackTrace

Config:

RxJava2Debug.enableRxJava2AssemblyTracking(newString[]{"com.akaita.fgas", "com.akaita.android"});

Result:

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 22538java.lang.Throwable: causedbyjava.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
[...]
Causedby: java.lang.Throwableatcom.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)
atandroid.app.Instrumentation.callActivityOnResume(Instrumentation.java:1269)
atandroid.app.Activity.performResume(Activity.java:6766)
atandroid.app.ActivityThread.performResumeActivity(ActivityThread.java:3377)
atandroid.app.ActivityThread.handleResumeActivity(ActivityThread.java:3440)
atandroid.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2713)
atandroid.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
atandroid.os.Handler.dispatchMessage(Handler.java:102)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)

This comes in very handy when you work with a error reporting system such as Crashlytics.

Instead of getting a multitude of reports involving RxJava2 into a single report entry, you'll get one entry per crash.

Credits

This library is using a subset of classes from akarnokd's RxJava2Extensions.

RxJava2Extensions takes about 1mb and contains 5231 methods. RxJava2Debug takes about 38kb and contains 239 methods.

About

RxJava 2.x extension to provide meaningful Stack Traces

Resources

Code of conduct

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

RxJava2Debug Tweet

LicenseMavenJcenterArsenalMethods

A library to make StackTraces involving RxJava2 more meaningful (they will always point to your code!).

Rationale

If you use RxJava2, you know the pain of debugging exceptions generated somwhere across the stream, in another thread, in a nested stream, ... The StackTrace provided by RxJava2 will tell you almost nothing. Even more, if you are using systems like Crashlytics to collect reports for and Android app, most RxJava-related crashes will be reported as a single error instance (omg, no way to fix that).

This library was created from the need to debug such situations when they happen in Android apps; even more, I needed to have a clear and precise report in Crashlytics for each distinct exception.

  1. If you handle exceptions generated in RxJava2 streams, you can query RxJava2Debug to obtain an extended StackTrace pointing to the exact line of code that created the issue.

  2. If you let exceptions generated in RxJava2 streams crash your app, RxJava2Debug will automatically extend the default StackTrace.

  3. If you let exceptions generated in RxJava2 streams crash your app and you configure RxJava2Debug with your package-names, RxJava2Debug will make sure you get unique reports for each issue (to Crashlytics, or whichever reporting system you use).

Installation

Using Maven Central :

repositories {
mavenCentral()
}
dependencies {
compile 'com.akaita.java:rxjava2-debug:1.4.0'
}

Using JCenter:

repositories {
jcenter()
}
dependencies {
compile 'com.akaita.java:rxjava2-debug:1.4.0'
}

Compatibility

RxJava2Debug 1.3.0+: RxJava 2.2.1+

RxJava2Debug 1.2.2: RxJava 2.1.0 to 2.2.0

Usage

Just enable RxJava2Debug as soon as possible. In Android, for example:

publicclassMyApplicationextendsApplication {
@OverridepublicvoidonCreate() {
super.onCreate();
Fabric.with(this, newCrashlytics());
// Enable RxJava assembly stack collection, to make RxJava crash reports clear and unique// Make sure this is called AFTER setting up any Crash reporting mechanism as CrashlyticsRxJava2Debug.enableRxJava2AssemblyTracking(newString[]{"com.example.myapp", "com.example.mylibrary"});
}
}

This will:

  • Enhance stack traces of all RxJava2-related crashes making sure they contain a reference to the method that generated the first event in the Rx pipeline
  • Make sure that Stack Traces contain a reference to your code (some class in com.example.myapp or com.example.mylibrary)
  • Make sure that crash reports in Crashlytics are actually different for each pipeline (avoid bundling every RXJava error into one reports)

You will now also be able to obtain an enhanced Stack Trace even when you implement onError (really, you should implement it):

responseSubject
.subscribe(
responseObservable -> handleResponse(responseObservable),
throwable -> RxJava2Debug.getEnhancedStackTrace(throwable)
);
Without RxJava2DebugWith RxJava2Debug
raw stack traceenhanced stack trace
raw crash reportenhanced crash report

API

Start collecting information about RxJava's execution to provide a more meaningful StackTrace in case of crash
Beware: Any crash-reporting handler should be set up before calling this method

voidenableRxJava2AssemblyTracking()

Start collecting filtered information about RxJava's execution to provide a more meaningful StackTrace in case of crash
Beware: Any crash-reporting handler should be set up before calling this method

voidenableRxJava2AssemblyTracking(@NullableString[] basePackageNames)

Disable the collection of more information about RxJava's execution
Information collected before calling this method will still be reported

voiddisableRxJava2AssemblyTracking()

Obtain a copy of the original Throwable with an extended StackTrace

@NullableThrowablegetEnhancedStackTrace(ThrowableoriginalException)

Features

StackTrace generation

Regardless of the thread a crash happens in, a nice StackTrace referring to the origin of the RxJava2 pipeline will be added to the causes of the crash.

Config:

RxJava2Debug.enableRxJava2AssemblyTracking();

Example (pointing to my own faulty code in com.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)):

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 22538java.lang.Throwable: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
athu.akarnokd.rxjava2.debug.ObservableOnAssembly$OnAssemblyObserver.onNext(ObservableOnAssembly.java:55)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
athu.akarnokd.rxjava2.debug.ObservableOnAssembly$OnAssemblyObserver.onNext(ObservableOnAssembly.java:55)
atio.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
atio.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:35)
atio.reactivex.Observable.subscribe(Observable.java:10838)
athu.akarnokd.rxjava2.debug.ObservableOnAssemblyScalarCallable.subscribeActual(ObservableOnAssemblyScalarCallable.java:41)
atio.reactivex.Observable.subscribe(Observable.java:10838)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
atio.reactivex.Scheduler$DisposeTask.run(Scheduler.java:452)
atio.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:61)
atio.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:52)
atjava.util.concurrent.FutureTask.run(FutureTask.java:237)
atjava.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
atjava.lang.Thread.run(Thread.java:761)
Causedby: causedbyjava.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atcom.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)
atandroid.app.Instrumentation.callActivityOnResume(Instrumentation.java:1269)
atandroid.app.Activity.performResume(Activity.java:6766)
atandroid.app.ActivityThread.performResumeActivity(ActivityThread.java:3377)
atandroid.app.ActivityThread.handleResumeActivity(ActivityThread.java:3440)
atandroid.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2713)
atandroid.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
atandroid.os.Handler.dispatchMessage(Handler.java:102)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)

Instead of the obscure:

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 27300io.reactivex.exceptions.OnErrorNotImplementedException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:704)
atio.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:701)
atio.reactivex.internal.observers.LambdaObserver.onError(LambdaObserver.java:74)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.checkTerminated(ObservableObserveOn.java:276)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:172)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:252)
atio.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:109)
atandroid.os.Handler.handleCallback(Handler.java:751)
atandroid.os.Handler.dispatchMessage(Handler.java:95)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atjava.lang.reflect.Method.invoke(NativeMethod)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
Causedby: java.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
atio.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
atio.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:35)
atio.reactivex.Observable.subscribe(Observable.java:10838)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
atio.reactivex.Scheduler$DisposeTask.run(Scheduler.java:452)
atio.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:61)
atio.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:52)
atjava.util.concurrent.FutureTask.run(FutureTask.java:237)
atjava.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
atjava.lang.Thread.run(Thread.java:761)

StackTrace filtering

Tell RxJava2Debug which packages names you want to highlight, and RxJava2Debug will make sure they appear as the top of the StackTrace

Config:

RxJava2Debug.enableRxJava2AssemblyTracking(newString[]{"com.akaita.fgas", "com.akaita.android"});

Result:

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 22538java.lang.Throwable: causedbyjava.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
[...]
Causedby: java.lang.Throwableatcom.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)
atandroid.app.Instrumentation.callActivityOnResume(Instrumentation.java:1269)
atandroid.app.Activity.performResume(Activity.java:6766)
atandroid.app.ActivityThread.performResumeActivity(ActivityThread.java:3377)
atandroid.app.ActivityThread.handleResumeActivity(ActivityThread.java:3440)
atandroid.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2713)
atandroid.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
atandroid.os.Handler.dispatchMessage(Handler.java:102)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)

This comes in very handy when you work with a error reporting system such as Crashlytics.

Instead of getting a multitude of reports involving RxJava2 into a single report entry, you'll get one entry per crash.

Credits

This library is using a subset of classes from akarnokd's RxJava2Extensions.

RxJava2Extensions takes about 1mb and contains 5231 methods. RxJava2Debug takes about 38kb and contains 239 methods.

About

RxJava 2.x extension to provide meaningful Stack Traces

Resources

Code of conduct

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

RxJava2Debug Tweet

LicenseMavenJcenterArsenalMethods

A library to make StackTraces involving RxJava2 more meaningful (they will always point to your code!).

Rationale

If you use RxJava2, you know the pain of debugging exceptions generated somwhere across the stream, in another thread, in a nested stream, ... The StackTrace provided by RxJava2 will tell you almost nothing. Even more, if you are using systems like Crashlytics to collect reports for and Android app, most RxJava-related crashes will be reported as a single error instance (omg, no way to fix that).

This library was created from the need to debug such situations when they happen in Android apps; even more, I needed to have a clear and precise report in Crashlytics for each distinct exception.

  1. If you handle exceptions generated in RxJava2 streams, you can query RxJava2Debug to obtain an extended StackTrace pointing to the exact line of code that created the issue.

  2. If you let exceptions generated in RxJava2 streams crash your app, RxJava2Debug will automatically extend the default StackTrace.

  3. If you let exceptions generated in RxJava2 streams crash your app and you configure RxJava2Debug with your package-names, RxJava2Debug will make sure you get unique reports for each issue (to Crashlytics, or whichever reporting system you use).

Installation

Using Maven Central :

repositories {
mavenCentral()
}
dependencies {
compile 'com.akaita.java:rxjava2-debug:1.4.0'
}

Using JCenter:

repositories {
jcenter()
}
dependencies {
compile 'com.akaita.java:rxjava2-debug:1.4.0'
}

Compatibility

RxJava2Debug 1.3.0+: RxJava 2.2.1+

RxJava2Debug 1.2.2: RxJava 2.1.0 to 2.2.0

Usage

Just enable RxJava2Debug as soon as possible. In Android, for example:

publicclassMyApplicationextendsApplication {
@OverridepublicvoidonCreate() {
super.onCreate();
Fabric.with(this, newCrashlytics());
// Enable RxJava assembly stack collection, to make RxJava crash reports clear and unique// Make sure this is called AFTER setting up any Crash reporting mechanism as CrashlyticsRxJava2Debug.enableRxJava2AssemblyTracking(newString[]{"com.example.myapp", "com.example.mylibrary"});
}
}

This will:

  • Enhance stack traces of all RxJava2-related crashes making sure they contain a reference to the method that generated the first event in the Rx pipeline
  • Make sure that Stack Traces contain a reference to your code (some class in com.example.myapp or com.example.mylibrary)
  • Make sure that crash reports in Crashlytics are actually different for each pipeline (avoid bundling every RXJava error into one reports)

You will now also be able to obtain an enhanced Stack Trace even when you implement onError (really, you should implement it):

responseSubject
.subscribe(
responseObservable -> handleResponse(responseObservable),
throwable -> RxJava2Debug.getEnhancedStackTrace(throwable)
);
Without RxJava2DebugWith RxJava2Debug
raw stack traceenhanced stack trace
raw crash reportenhanced crash report

API

Start collecting information about RxJava's execution to provide a more meaningful StackTrace in case of crash
Beware: Any crash-reporting handler should be set up before calling this method

voidenableRxJava2AssemblyTracking()

Start collecting filtered information about RxJava's execution to provide a more meaningful StackTrace in case of crash
Beware: Any crash-reporting handler should be set up before calling this method

voidenableRxJava2AssemblyTracking(@NullableString[] basePackageNames)

Disable the collection of more information about RxJava's execution
Information collected before calling this method will still be reported

voiddisableRxJava2AssemblyTracking()

Obtain a copy of the original Throwable with an extended StackTrace

@NullableThrowablegetEnhancedStackTrace(ThrowableoriginalException)

Features

StackTrace generation

Regardless of the thread a crash happens in, a nice StackTrace referring to the origin of the RxJava2 pipeline will be added to the causes of the crash.

Config:

RxJava2Debug.enableRxJava2AssemblyTracking();

Example (pointing to my own faulty code in com.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)):

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 22538java.lang.Throwable: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
athu.akarnokd.rxjava2.debug.ObservableOnAssembly$OnAssemblyObserver.onNext(ObservableOnAssembly.java:55)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
athu.akarnokd.rxjava2.debug.ObservableOnAssembly$OnAssemblyObserver.onNext(ObservableOnAssembly.java:55)
atio.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
atio.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:35)
atio.reactivex.Observable.subscribe(Observable.java:10838)
athu.akarnokd.rxjava2.debug.ObservableOnAssemblyScalarCallable.subscribeActual(ObservableOnAssemblyScalarCallable.java:41)
atio.reactivex.Observable.subscribe(Observable.java:10838)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
atio.reactivex.Scheduler$DisposeTask.run(Scheduler.java:452)
atio.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:61)
atio.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:52)
atjava.util.concurrent.FutureTask.run(FutureTask.java:237)
atjava.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
atjava.lang.Thread.run(Thread.java:761)
Causedby: causedbyjava.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atcom.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)
atandroid.app.Instrumentation.callActivityOnResume(Instrumentation.java:1269)
atandroid.app.Activity.performResume(Activity.java:6766)
atandroid.app.ActivityThread.performResumeActivity(ActivityThread.java:3377)
atandroid.app.ActivityThread.handleResumeActivity(ActivityThread.java:3440)
atandroid.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2713)
atandroid.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
atandroid.os.Handler.dispatchMessage(Handler.java:102)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)

Instead of the obscure:

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 27300io.reactivex.exceptions.OnErrorNotImplementedException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:704)
atio.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:701)
atio.reactivex.internal.observers.LambdaObserver.onError(LambdaObserver.java:74)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.checkTerminated(ObservableObserveOn.java:276)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:172)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:252)
atio.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:109)
atandroid.os.Handler.handleCallback(Handler.java:751)
atandroid.os.Handler.dispatchMessage(Handler.java:95)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atjava.lang.reflect.Method.invoke(NativeMethod)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
Causedby: java.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
atio.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
atio.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:35)
atio.reactivex.Observable.subscribe(Observable.java:10838)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
atio.reactivex.Scheduler$DisposeTask.run(Scheduler.java:452)
atio.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:61)
atio.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:52)
atjava.util.concurrent.FutureTask.run(FutureTask.java:237)
atjava.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
atjava.lang.Thread.run(Thread.java:761)

StackTrace filtering

Tell RxJava2Debug which packages names you want to highlight, and RxJava2Debug will make sure they appear as the top of the StackTrace

Config:

RxJava2Debug.enableRxJava2AssemblyTracking(newString[]{"com.akaita.fgas", "com.akaita.android"});

Result:

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 22538java.lang.Throwable: causedbyjava.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
[...]
Causedby: java.lang.Throwableatcom.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)
atandroid.app.Instrumentation.callActivityOnResume(Instrumentation.java:1269)
atandroid.app.Activity.performResume(Activity.java:6766)
atandroid.app.ActivityThread.performResumeActivity(ActivityThread.java:3377)
atandroid.app.ActivityThread.handleResumeActivity(ActivityThread.java:3440)
atandroid.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2713)
atandroid.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
atandroid.os.Handler.dispatchMessage(Handler.java:102)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)

This comes in very handy when you work with a error reporting system such as Crashlytics.

Instead of getting a multitude of reports involving RxJava2 into a single report entry, you'll get one entry per crash.

Credits

This library is using a subset of classes from akarnokd's RxJava2Extensions.

RxJava2Extensions takes about 1mb and contains 5231 methods. RxJava2Debug takes about 38kb and contains 239 methods.

About

RxJava 2.x extension to provide meaningful Stack Traces

Resources

Code of conduct

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

RxJava2Debug Tweet

LicenseMavenJcenterArsenalMethods

A library to make StackTraces involving RxJava2 more meaningful (they will always point to your code!).

Rationale

If you use RxJava2, you know the pain of debugging exceptions generated somwhere across the stream, in another thread, in a nested stream, ... The StackTrace provided by RxJava2 will tell you almost nothing. Even more, if you are using systems like Crashlytics to collect reports for and Android app, most RxJava-related crashes will be reported as a single error instance (omg, no way to fix that).

This library was created from the need to debug such situations when they happen in Android apps; even more, I needed to have a clear and precise report in Crashlytics for each distinct exception.

  1. If you handle exceptions generated in RxJava2 streams, you can query RxJava2Debug to obtain an extended StackTrace pointing to the exact line of code that created the issue.

  2. If you let exceptions generated in RxJava2 streams crash your app, RxJava2Debug will automatically extend the default StackTrace.

  3. If you let exceptions generated in RxJava2 streams crash your app and you configure RxJava2Debug with your package-names, RxJava2Debug will make sure you get unique reports for each issue (to Crashlytics, or whichever reporting system you use).

Installation

Using Maven Central :

repositories {
mavenCentral()
}
dependencies {
compile 'com.akaita.java:rxjava2-debug:1.4.0'
}

Using JCenter:

repositories {
jcenter()
}
dependencies {
compile 'com.akaita.java:rxjava2-debug:1.4.0'
}

Compatibility

RxJava2Debug 1.3.0+: RxJava 2.2.1+

RxJava2Debug 1.2.2: RxJava 2.1.0 to 2.2.0

Usage

Just enable RxJava2Debug as soon as possible. In Android, for example:

publicclassMyApplicationextendsApplication {
@OverridepublicvoidonCreate() {
super.onCreate();
Fabric.with(this, newCrashlytics());
// Enable RxJava assembly stack collection, to make RxJava crash reports clear and unique// Make sure this is called AFTER setting up any Crash reporting mechanism as CrashlyticsRxJava2Debug.enableRxJava2AssemblyTracking(newString[]{"com.example.myapp", "com.example.mylibrary"});
}
}

This will:

  • Enhance stack traces of all RxJava2-related crashes making sure they contain a reference to the method that generated the first event in the Rx pipeline
  • Make sure that Stack Traces contain a reference to your code (some class in com.example.myapp or com.example.mylibrary)
  • Make sure that crash reports in Crashlytics are actually different for each pipeline (avoid bundling every RXJava error into one reports)

You will now also be able to obtain an enhanced Stack Trace even when you implement onError (really, you should implement it):

responseSubject
.subscribe(
responseObservable -> handleResponse(responseObservable),
throwable -> RxJava2Debug.getEnhancedStackTrace(throwable)
);
Without RxJava2DebugWith RxJava2Debug
raw stack traceenhanced stack trace
raw crash reportenhanced crash report

API

Start collecting information about RxJava's execution to provide a more meaningful StackTrace in case of crash
Beware: Any crash-reporting handler should be set up before calling this method

voidenableRxJava2AssemblyTracking()

Start collecting filtered information about RxJava's execution to provide a more meaningful StackTrace in case of crash
Beware: Any crash-reporting handler should be set up before calling this method

voidenableRxJava2AssemblyTracking(@NullableString[] basePackageNames)

Disable the collection of more information about RxJava's execution
Information collected before calling this method will still be reported

voiddisableRxJava2AssemblyTracking()

Obtain a copy of the original Throwable with an extended StackTrace

@NullableThrowablegetEnhancedStackTrace(ThrowableoriginalException)

Features

StackTrace generation

Regardless of the thread a crash happens in, a nice StackTrace referring to the origin of the RxJava2 pipeline will be added to the causes of the crash.

Config:

RxJava2Debug.enableRxJava2AssemblyTracking();

Example (pointing to my own faulty code in com.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)):

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 22538java.lang.Throwable: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
athu.akarnokd.rxjava2.debug.ObservableOnAssembly$OnAssemblyObserver.onNext(ObservableOnAssembly.java:55)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
athu.akarnokd.rxjava2.debug.ObservableOnAssembly$OnAssemblyObserver.onNext(ObservableOnAssembly.java:55)
atio.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
atio.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:35)
atio.reactivex.Observable.subscribe(Observable.java:10838)
athu.akarnokd.rxjava2.debug.ObservableOnAssemblyScalarCallable.subscribeActual(ObservableOnAssemblyScalarCallable.java:41)
atio.reactivex.Observable.subscribe(Observable.java:10838)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
atio.reactivex.Scheduler$DisposeTask.run(Scheduler.java:452)
atio.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:61)
atio.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:52)
atjava.util.concurrent.FutureTask.run(FutureTask.java:237)
atjava.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
atjava.lang.Thread.run(Thread.java:761)
Causedby: causedbyjava.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atcom.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)
atandroid.app.Instrumentation.callActivityOnResume(Instrumentation.java:1269)
atandroid.app.Activity.performResume(Activity.java:6766)
atandroid.app.ActivityThread.performResumeActivity(ActivityThread.java:3377)
atandroid.app.ActivityThread.handleResumeActivity(ActivityThread.java:3440)
atandroid.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2713)
atandroid.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
atandroid.os.Handler.dispatchMessage(Handler.java:102)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)

Instead of the obscure:

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 27300io.reactivex.exceptions.OnErrorNotImplementedException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:704)
atio.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:701)
atio.reactivex.internal.observers.LambdaObserver.onError(LambdaObserver.java:74)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.checkTerminated(ObservableObserveOn.java:276)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:172)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:252)
atio.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:109)
atandroid.os.Handler.handleCallback(Handler.java:751)
atandroid.os.Handler.dispatchMessage(Handler.java:95)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atjava.lang.reflect.Method.invoke(NativeMethod)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
Causedby: java.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
atio.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
atio.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:35)
atio.reactivex.Observable.subscribe(Observable.java:10838)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
atio.reactivex.Scheduler$DisposeTask.run(Scheduler.java:452)
atio.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:61)
atio.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:52)
atjava.util.concurrent.FutureTask.run(FutureTask.java:237)
atjava.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
atjava.lang.Thread.run(Thread.java:761)

StackTrace filtering

Tell RxJava2Debug which packages names you want to highlight, and RxJava2Debug will make sure they appear as the top of the StackTrace

Config:

RxJava2Debug.enableRxJava2AssemblyTracking(newString[]{"com.akaita.fgas", "com.akaita.android"});

Result:

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 22538java.lang.Throwable: causedbyjava.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
[...]
Causedby: java.lang.Throwableatcom.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)
atandroid.app.Instrumentation.callActivityOnResume(Instrumentation.java:1269)
atandroid.app.Activity.performResume(Activity.java:6766)
atandroid.app.ActivityThread.performResumeActivity(ActivityThread.java:3377)
atandroid.app.ActivityThread.handleResumeActivity(ActivityThread.java:3440)
atandroid.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2713)
atandroid.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
atandroid.os.Handler.dispatchMessage(Handler.java:102)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)

This comes in very handy when you work with a error reporting system such as Crashlytics.

Instead of getting a multitude of reports involving RxJava2 into a single report entry, you'll get one entry per crash.

Credits

This library is using a subset of classes from akarnokd's RxJava2Extensions.

RxJava2Extensions takes about 1mb and contains 5231 methods. RxJava2Debug takes about 38kb and contains 239 methods.

About

RxJava 2.x extension to provide meaningful Stack Traces

Resources

Code of conduct

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

RxJava2Debug Tweet

LicenseMavenJcenterArsenalMethods

A library to make StackTraces involving RxJava2 more meaningful (they will always point to your code!).

Rationale

If you use RxJava2, you know the pain of debugging exceptions generated somwhere across the stream, in another thread, in a nested stream, ... The StackTrace provided by RxJava2 will tell you almost nothing. Even more, if you are using systems like Crashlytics to collect reports for and Android app, most RxJava-related crashes will be reported as a single error instance (omg, no way to fix that).

This library was created from the need to debug such situations when they happen in Android apps; even more, I needed to have a clear and precise report in Crashlytics for each distinct exception.

  1. If you handle exceptions generated in RxJava2 streams, you can query RxJava2Debug to obtain an extended StackTrace pointing to the exact line of code that created the issue.

  2. If you let exceptions generated in RxJava2 streams crash your app, RxJava2Debug will automatically extend the default StackTrace.

  3. If you let exceptions generated in RxJava2 streams crash your app and you configure RxJava2Debug with your package-names, RxJava2Debug will make sure you get unique reports for each issue (to Crashlytics, or whichever reporting system you use).

Installation

Using Maven Central :

repositories {
mavenCentral()
}
dependencies {
compile 'com.akaita.java:rxjava2-debug:1.4.0'
}

Using JCenter:

repositories {
jcenter()
}
dependencies {
compile 'com.akaita.java:rxjava2-debug:1.4.0'
}

Compatibility

RxJava2Debug 1.3.0+: RxJava 2.2.1+

RxJava2Debug 1.2.2: RxJava 2.1.0 to 2.2.0

Usage

Just enable RxJava2Debug as soon as possible. In Android, for example:

publicclassMyApplicationextendsApplication {
@OverridepublicvoidonCreate() {
super.onCreate();
Fabric.with(this, newCrashlytics());
// Enable RxJava assembly stack collection, to make RxJava crash reports clear and unique// Make sure this is called AFTER setting up any Crash reporting mechanism as CrashlyticsRxJava2Debug.enableRxJava2AssemblyTracking(newString[]{"com.example.myapp", "com.example.mylibrary"});
}
}

This will:

  • Enhance stack traces of all RxJava2-related crashes making sure they contain a reference to the method that generated the first event in the Rx pipeline
  • Make sure that Stack Traces contain a reference to your code (some class in com.example.myapp or com.example.mylibrary)
  • Make sure that crash reports in Crashlytics are actually different for each pipeline (avoid bundling every RXJava error into one reports)

You will now also be able to obtain an enhanced Stack Trace even when you implement onError (really, you should implement it):

responseSubject
.subscribe(
responseObservable -> handleResponse(responseObservable),
throwable -> RxJava2Debug.getEnhancedStackTrace(throwable)
);
Without RxJava2DebugWith RxJava2Debug
raw stack traceenhanced stack trace
raw crash reportenhanced crash report

API

Start collecting information about RxJava's execution to provide a more meaningful StackTrace in case of crash
Beware: Any crash-reporting handler should be set up before calling this method

voidenableRxJava2AssemblyTracking()

Start collecting filtered information about RxJava's execution to provide a more meaningful StackTrace in case of crash
Beware: Any crash-reporting handler should be set up before calling this method

voidenableRxJava2AssemblyTracking(@NullableString[] basePackageNames)

Disable the collection of more information about RxJava's execution
Information collected before calling this method will still be reported

voiddisableRxJava2AssemblyTracking()

Obtain a copy of the original Throwable with an extended StackTrace

@NullableThrowablegetEnhancedStackTrace(ThrowableoriginalException)

Features

StackTrace generation

Regardless of the thread a crash happens in, a nice StackTrace referring to the origin of the RxJava2 pipeline will be added to the causes of the crash.

Config:

RxJava2Debug.enableRxJava2AssemblyTracking();

Example (pointing to my own faulty code in com.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)):

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 22538java.lang.Throwable: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
athu.akarnokd.rxjava2.debug.ObservableOnAssembly$OnAssemblyObserver.onNext(ObservableOnAssembly.java:55)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
athu.akarnokd.rxjava2.debug.ObservableOnAssembly$OnAssemblyObserver.onNext(ObservableOnAssembly.java:55)
atio.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
atio.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:35)
atio.reactivex.Observable.subscribe(Observable.java:10838)
athu.akarnokd.rxjava2.debug.ObservableOnAssemblyScalarCallable.subscribeActual(ObservableOnAssemblyScalarCallable.java:41)
atio.reactivex.Observable.subscribe(Observable.java:10838)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
atio.reactivex.Scheduler$DisposeTask.run(Scheduler.java:452)
atio.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:61)
atio.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:52)
atjava.util.concurrent.FutureTask.run(FutureTask.java:237)
atjava.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
atjava.lang.Thread.run(Thread.java:761)
Causedby: causedbyjava.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atcom.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)
atandroid.app.Instrumentation.callActivityOnResume(Instrumentation.java:1269)
atandroid.app.Activity.performResume(Activity.java:6766)
atandroid.app.ActivityThread.performResumeActivity(ActivityThread.java:3377)
atandroid.app.ActivityThread.handleResumeActivity(ActivityThread.java:3440)
atandroid.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2713)
atandroid.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
atandroid.os.Handler.dispatchMessage(Handler.java:102)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)

Instead of the obscure:

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 27300io.reactivex.exceptions.OnErrorNotImplementedException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:704)
atio.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:701)
atio.reactivex.internal.observers.LambdaObserver.onError(LambdaObserver.java:74)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.checkTerminated(ObservableObserveOn.java:276)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:172)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:252)
atio.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:109)
atandroid.os.Handler.handleCallback(Handler.java:751)
atandroid.os.Handler.dispatchMessage(Handler.java:95)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atjava.lang.reflect.Method.invoke(NativeMethod)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
Causedby: java.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
atio.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
atio.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:35)
atio.reactivex.Observable.subscribe(Observable.java:10838)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
atio.reactivex.Scheduler$DisposeTask.run(Scheduler.java:452)
atio.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:61)
atio.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:52)
atjava.util.concurrent.FutureTask.run(FutureTask.java:237)
atjava.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
atjava.lang.Thread.run(Thread.java:761)

StackTrace filtering

Tell RxJava2Debug which packages names you want to highlight, and RxJava2Debug will make sure they appear as the top of the StackTrace

Config:

RxJava2Debug.enableRxJava2AssemblyTracking(newString[]{"com.akaita.fgas", "com.akaita.android"});

Result:

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 22538java.lang.Throwable: causedbyjava.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
[...]
Causedby: java.lang.Throwableatcom.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)
atandroid.app.Instrumentation.callActivityOnResume(Instrumentation.java:1269)
atandroid.app.Activity.performResume(Activity.java:6766)
atandroid.app.ActivityThread.performResumeActivity(ActivityThread.java:3377)
atandroid.app.ActivityThread.handleResumeActivity(ActivityThread.java:3440)
atandroid.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2713)
atandroid.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
atandroid.os.Handler.dispatchMessage(Handler.java:102)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)

This comes in very handy when you work with a error reporting system such as Crashlytics.

Instead of getting a multitude of reports involving RxJava2 into a single report entry, you'll get one entry per crash.

Credits

This library is using a subset of classes from akarnokd's RxJava2Extensions.

RxJava2Extensions takes about 1mb and contains 5231 methods. RxJava2Debug takes about 38kb and contains 239 methods.

About

RxJava 2.x extension to provide meaningful Stack Traces

Resources

Code of conduct

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

RxJava2Debug Tweet

LicenseMavenJcenterArsenalMethods

A library to make StackTraces involving RxJava2 more meaningful (they will always point to your code!).

Rationale

If you use RxJava2, you know the pain of debugging exceptions generated somwhere across the stream, in another thread, in a nested stream, ... The StackTrace provided by RxJava2 will tell you almost nothing. Even more, if you are using systems like Crashlytics to collect reports for and Android app, most RxJava-related crashes will be reported as a single error instance (omg, no way to fix that).

This library was created from the need to debug such situations when they happen in Android apps; even more, I needed to have a clear and precise report in Crashlytics for each distinct exception.

  1. If you handle exceptions generated in RxJava2 streams, you can query RxJava2Debug to obtain an extended StackTrace pointing to the exact line of code that created the issue.

  2. If you let exceptions generated in RxJava2 streams crash your app, RxJava2Debug will automatically extend the default StackTrace.

  3. If you let exceptions generated in RxJava2 streams crash your app and you configure RxJava2Debug with your package-names, RxJava2Debug will make sure you get unique reports for each issue (to Crashlytics, or whichever reporting system you use).

Installation

Using Maven Central :

repositories {
mavenCentral()
}
dependencies {
compile 'com.akaita.java:rxjava2-debug:1.4.0'
}

Using JCenter:

repositories {
jcenter()
}
dependencies {
compile 'com.akaita.java:rxjava2-debug:1.4.0'
}

Compatibility

RxJava2Debug 1.3.0+: RxJava 2.2.1+

RxJava2Debug 1.2.2: RxJava 2.1.0 to 2.2.0

Usage

Just enable RxJava2Debug as soon as possible. In Android, for example:

publicclassMyApplicationextendsApplication {
@OverridepublicvoidonCreate() {
super.onCreate();
Fabric.with(this, newCrashlytics());
// Enable RxJava assembly stack collection, to make RxJava crash reports clear and unique// Make sure this is called AFTER setting up any Crash reporting mechanism as CrashlyticsRxJava2Debug.enableRxJava2AssemblyTracking(newString[]{"com.example.myapp", "com.example.mylibrary"});
}
}

This will:

  • Enhance stack traces of all RxJava2-related crashes making sure they contain a reference to the method that generated the first event in the Rx pipeline
  • Make sure that Stack Traces contain a reference to your code (some class in com.example.myapp or com.example.mylibrary)
  • Make sure that crash reports in Crashlytics are actually different for each pipeline (avoid bundling every RXJava error into one reports)

You will now also be able to obtain an enhanced Stack Trace even when you implement onError (really, you should implement it):

responseSubject
.subscribe(
responseObservable -> handleResponse(responseObservable),
throwable -> RxJava2Debug.getEnhancedStackTrace(throwable)
);
Without RxJava2DebugWith RxJava2Debug
raw stack traceenhanced stack trace
raw crash reportenhanced crash report

API

Start collecting information about RxJava's execution to provide a more meaningful StackTrace in case of crash
Beware: Any crash-reporting handler should be set up before calling this method

voidenableRxJava2AssemblyTracking()

Start collecting filtered information about RxJava's execution to provide a more meaningful StackTrace in case of crash
Beware: Any crash-reporting handler should be set up before calling this method

voidenableRxJava2AssemblyTracking(@NullableString[] basePackageNames)

Disable the collection of more information about RxJava's execution
Information collected before calling this method will still be reported

voiddisableRxJava2AssemblyTracking()

Obtain a copy of the original Throwable with an extended StackTrace

@NullableThrowablegetEnhancedStackTrace(ThrowableoriginalException)

Features

StackTrace generation

Regardless of the thread a crash happens in, a nice StackTrace referring to the origin of the RxJava2 pipeline will be added to the causes of the crash.

Config:

RxJava2Debug.enableRxJava2AssemblyTracking();

Example (pointing to my own faulty code in com.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)):

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 22538java.lang.Throwable: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
athu.akarnokd.rxjava2.debug.ObservableOnAssembly$OnAssemblyObserver.onNext(ObservableOnAssembly.java:55)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
athu.akarnokd.rxjava2.debug.ObservableOnAssembly$OnAssemblyObserver.onNext(ObservableOnAssembly.java:55)
atio.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
atio.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:35)
atio.reactivex.Observable.subscribe(Observable.java:10838)
athu.akarnokd.rxjava2.debug.ObservableOnAssemblyScalarCallable.subscribeActual(ObservableOnAssemblyScalarCallable.java:41)
atio.reactivex.Observable.subscribe(Observable.java:10838)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
atio.reactivex.Scheduler$DisposeTask.run(Scheduler.java:452)
atio.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:61)
atio.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:52)
atjava.util.concurrent.FutureTask.run(FutureTask.java:237)
atjava.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
atjava.lang.Thread.run(Thread.java:761)
Causedby: causedbyjava.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atcom.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)
atandroid.app.Instrumentation.callActivityOnResume(Instrumentation.java:1269)
atandroid.app.Activity.performResume(Activity.java:6766)
atandroid.app.ActivityThread.performResumeActivity(ActivityThread.java:3377)
atandroid.app.ActivityThread.handleResumeActivity(ActivityThread.java:3440)
atandroid.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2713)
atandroid.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
atandroid.os.Handler.dispatchMessage(Handler.java:102)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)

Instead of the obscure:

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 27300io.reactivex.exceptions.OnErrorNotImplementedException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:704)
atio.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:701)
atio.reactivex.internal.observers.LambdaObserver.onError(LambdaObserver.java:74)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.checkTerminated(ObservableObserveOn.java:276)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:172)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:252)
atio.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:109)
atandroid.os.Handler.handleCallback(Handler.java:751)
atandroid.os.Handler.dispatchMessage(Handler.java:95)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atjava.lang.reflect.Method.invoke(NativeMethod)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
Causedby: java.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
atio.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
atio.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:35)
atio.reactivex.Observable.subscribe(Observable.java:10838)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
atio.reactivex.Scheduler$DisposeTask.run(Scheduler.java:452)
atio.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:61)
atio.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:52)
atjava.util.concurrent.FutureTask.run(FutureTask.java:237)
atjava.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
atjava.lang.Thread.run(Thread.java:761)

StackTrace filtering

Tell RxJava2Debug which packages names you want to highlight, and RxJava2Debug will make sure they appear as the top of the StackTrace

Config:

RxJava2Debug.enableRxJava2AssemblyTracking(newString[]{"com.akaita.fgas", "com.akaita.android"});

Result:

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 22538java.lang.Throwable: causedbyjava.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
[...]
Causedby: java.lang.Throwableatcom.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)
atandroid.app.Instrumentation.callActivityOnResume(Instrumentation.java:1269)
atandroid.app.Activity.performResume(Activity.java:6766)
atandroid.app.ActivityThread.performResumeActivity(ActivityThread.java:3377)
atandroid.app.ActivityThread.handleResumeActivity(ActivityThread.java:3440)
atandroid.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2713)
atandroid.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
atandroid.os.Handler.dispatchMessage(Handler.java:102)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)

This comes in very handy when you work with a error reporting system such as Crashlytics.

Instead of getting a multitude of reports involving RxJava2 into a single report entry, you'll get one entry per crash.

Credits

This library is using a subset of classes from akarnokd's RxJava2Extensions.

RxJava2Extensions takes about 1mb and contains 5231 methods. RxJava2Debug takes about 38kb and contains 239 methods.

About

RxJava 2.x extension to provide meaningful Stack Traces

Resources

Code of conduct

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

RxJava2Debug Tweet

LicenseMavenJcenterArsenalMethods

A library to make StackTraces involving RxJava2 more meaningful (they will always point to your code!).

Rationale

If you use RxJava2, you know the pain of debugging exceptions generated somwhere across the stream, in another thread, in a nested stream, ... The StackTrace provided by RxJava2 will tell you almost nothing. Even more, if you are using systems like Crashlytics to collect reports for and Android app, most RxJava-related crashes will be reported as a single error instance (omg, no way to fix that).

This library was created from the need to debug such situations when they happen in Android apps; even more, I needed to have a clear and precise report in Crashlytics for each distinct exception.

  1. If you handle exceptions generated in RxJava2 streams, you can query RxJava2Debug to obtain an extended StackTrace pointing to the exact line of code that created the issue.

  2. If you let exceptions generated in RxJava2 streams crash your app, RxJava2Debug will automatically extend the default StackTrace.

  3. If you let exceptions generated in RxJava2 streams crash your app and you configure RxJava2Debug with your package-names, RxJava2Debug will make sure you get unique reports for each issue (to Crashlytics, or whichever reporting system you use).

Installation

Using Maven Central :

repositories {
mavenCentral()
}
dependencies {
compile 'com.akaita.java:rxjava2-debug:1.4.0'
}

Using JCenter:

repositories {
jcenter()
}
dependencies {
compile 'com.akaita.java:rxjava2-debug:1.4.0'
}

Compatibility

RxJava2Debug 1.3.0+: RxJava 2.2.1+

RxJava2Debug 1.2.2: RxJava 2.1.0 to 2.2.0

Usage

Just enable RxJava2Debug as soon as possible. In Android, for example:

publicclassMyApplicationextendsApplication {
@OverridepublicvoidonCreate() {
super.onCreate();
Fabric.with(this, newCrashlytics());
// Enable RxJava assembly stack collection, to make RxJava crash reports clear and unique// Make sure this is called AFTER setting up any Crash reporting mechanism as CrashlyticsRxJava2Debug.enableRxJava2AssemblyTracking(newString[]{"com.example.myapp", "com.example.mylibrary"});
}
}

This will:

  • Enhance stack traces of all RxJava2-related crashes making sure they contain a reference to the method that generated the first event in the Rx pipeline
  • Make sure that Stack Traces contain a reference to your code (some class in com.example.myapp or com.example.mylibrary)
  • Make sure that crash reports in Crashlytics are actually different for each pipeline (avoid bundling every RXJava error into one reports)

You will now also be able to obtain an enhanced Stack Trace even when you implement onError (really, you should implement it):

responseSubject
.subscribe(
responseObservable -> handleResponse(responseObservable),
throwable -> RxJava2Debug.getEnhancedStackTrace(throwable)
);
Without RxJava2DebugWith RxJava2Debug
raw stack traceenhanced stack trace
raw crash reportenhanced crash report

API

Start collecting information about RxJava's execution to provide a more meaningful StackTrace in case of crash
Beware: Any crash-reporting handler should be set up before calling this method

voidenableRxJava2AssemblyTracking()

Start collecting filtered information about RxJava's execution to provide a more meaningful StackTrace in case of crash
Beware: Any crash-reporting handler should be set up before calling this method

voidenableRxJava2AssemblyTracking(@NullableString[] basePackageNames)

Disable the collection of more information about RxJava's execution
Information collected before calling this method will still be reported

voiddisableRxJava2AssemblyTracking()

Obtain a copy of the original Throwable with an extended StackTrace

@NullableThrowablegetEnhancedStackTrace(ThrowableoriginalException)

Features

StackTrace generation

Regardless of the thread a crash happens in, a nice StackTrace referring to the origin of the RxJava2 pipeline will be added to the causes of the crash.

Config:

RxJava2Debug.enableRxJava2AssemblyTracking();

Example (pointing to my own faulty code in com.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)):

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 22538java.lang.Throwable: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
athu.akarnokd.rxjava2.debug.ObservableOnAssembly$OnAssemblyObserver.onNext(ObservableOnAssembly.java:55)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
athu.akarnokd.rxjava2.debug.ObservableOnAssembly$OnAssemblyObserver.onNext(ObservableOnAssembly.java:55)
atio.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
atio.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:35)
atio.reactivex.Observable.subscribe(Observable.java:10838)
athu.akarnokd.rxjava2.debug.ObservableOnAssemblyScalarCallable.subscribeActual(ObservableOnAssemblyScalarCallable.java:41)
atio.reactivex.Observable.subscribe(Observable.java:10838)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
atio.reactivex.Scheduler$DisposeTask.run(Scheduler.java:452)
atio.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:61)
atio.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:52)
atjava.util.concurrent.FutureTask.run(FutureTask.java:237)
atjava.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
atjava.lang.Thread.run(Thread.java:761)
Causedby: causedbyjava.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atcom.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)
atandroid.app.Instrumentation.callActivityOnResume(Instrumentation.java:1269)
atandroid.app.Activity.performResume(Activity.java:6766)
atandroid.app.ActivityThread.performResumeActivity(ActivityThread.java:3377)
atandroid.app.ActivityThread.handleResumeActivity(ActivityThread.java:3440)
atandroid.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2713)
atandroid.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
atandroid.os.Handler.dispatchMessage(Handler.java:102)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)

Instead of the obscure:

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 27300io.reactivex.exceptions.OnErrorNotImplementedException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:704)
atio.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:701)
atio.reactivex.internal.observers.LambdaObserver.onError(LambdaObserver.java:74)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.checkTerminated(ObservableObserveOn.java:276)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:172)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:252)
atio.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:109)
atandroid.os.Handler.handleCallback(Handler.java:751)
atandroid.os.Handler.dispatchMessage(Handler.java:95)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atjava.lang.reflect.Method.invoke(NativeMethod)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
Causedby: java.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
atio.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
atio.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:35)
atio.reactivex.Observable.subscribe(Observable.java:10838)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
atio.reactivex.Scheduler$DisposeTask.run(Scheduler.java:452)
atio.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:61)
atio.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:52)
atjava.util.concurrent.FutureTask.run(FutureTask.java:237)
atjava.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
atjava.lang.Thread.run(Thread.java:761)

StackTrace filtering

Tell RxJava2Debug which packages names you want to highlight, and RxJava2Debug will make sure they appear as the top of the StackTrace

Config:

RxJava2Debug.enableRxJava2AssemblyTracking(newString[]{"com.akaita.fgas", "com.akaita.android"});

Result:

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 22538java.lang.Throwable: causedbyjava.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
[...]
Causedby: java.lang.Throwableatcom.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)
atandroid.app.Instrumentation.callActivityOnResume(Instrumentation.java:1269)
atandroid.app.Activity.performResume(Activity.java:6766)
atandroid.app.ActivityThread.performResumeActivity(ActivityThread.java:3377)
atandroid.app.ActivityThread.handleResumeActivity(ActivityThread.java:3440)
atandroid.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2713)
atandroid.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
atandroid.os.Handler.dispatchMessage(Handler.java:102)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)

This comes in very handy when you work with a error reporting system such as Crashlytics.

Instead of getting a multitude of reports involving RxJava2 into a single report entry, you'll get one entry per crash.

Credits

This library is using a subset of classes from akarnokd's RxJava2Extensions.

RxJava2Extensions takes about 1mb and contains 5231 methods. RxJava2Debug takes about 38kb and contains 239 methods.

About

RxJava 2.x extension to provide meaningful Stack Traces

Resources

Code of conduct

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

RxJava2Debug Tweet

LicenseMavenJcenterArsenalMethods

A library to make StackTraces involving RxJava2 more meaningful (they will always point to your code!).

Rationale

If you use RxJava2, you know the pain of debugging exceptions generated somwhere across the stream, in another thread, in a nested stream, ... The StackTrace provided by RxJava2 will tell you almost nothing. Even more, if you are using systems like Crashlytics to collect reports for and Android app, most RxJava-related crashes will be reported as a single error instance (omg, no way to fix that).

This library was created from the need to debug such situations when they happen in Android apps; even more, I needed to have a clear and precise report in Crashlytics for each distinct exception.

  1. If you handle exceptions generated in RxJava2 streams, you can query RxJava2Debug to obtain an extended StackTrace pointing to the exact line of code that created the issue.

  2. If you let exceptions generated in RxJava2 streams crash your app, RxJava2Debug will automatically extend the default StackTrace.

  3. If you let exceptions generated in RxJava2 streams crash your app and you configure RxJava2Debug with your package-names, RxJava2Debug will make sure you get unique reports for each issue (to Crashlytics, or whichever reporting system you use).

Installation

Using Maven Central :

repositories {
mavenCentral()
}
dependencies {
compile 'com.akaita.java:rxjava2-debug:1.4.0'
}

Using JCenter:

repositories {
jcenter()
}
dependencies {
compile 'com.akaita.java:rxjava2-debug:1.4.0'
}

Compatibility

RxJava2Debug 1.3.0+: RxJava 2.2.1+

RxJava2Debug 1.2.2: RxJava 2.1.0 to 2.2.0

Usage

Just enable RxJava2Debug as soon as possible. In Android, for example:

publicclassMyApplicationextendsApplication {
@OverridepublicvoidonCreate() {
super.onCreate();
Fabric.with(this, newCrashlytics());
// Enable RxJava assembly stack collection, to make RxJava crash reports clear and unique// Make sure this is called AFTER setting up any Crash reporting mechanism as CrashlyticsRxJava2Debug.enableRxJava2AssemblyTracking(newString[]{"com.example.myapp", "com.example.mylibrary"});
}
}

This will:

  • Enhance stack traces of all RxJava2-related crashes making sure they contain a reference to the method that generated the first event in the Rx pipeline
  • Make sure that Stack Traces contain a reference to your code (some class in com.example.myapp or com.example.mylibrary)
  • Make sure that crash reports in Crashlytics are actually different for each pipeline (avoid bundling every RXJava error into one reports)

You will now also be able to obtain an enhanced Stack Trace even when you implement onError (really, you should implement it):

responseSubject
.subscribe(
responseObservable -> handleResponse(responseObservable),
throwable -> RxJava2Debug.getEnhancedStackTrace(throwable)
);
Without RxJava2DebugWith RxJava2Debug
raw stack traceenhanced stack trace
raw crash reportenhanced crash report

API

Start collecting information about RxJava's execution to provide a more meaningful StackTrace in case of crash
Beware: Any crash-reporting handler should be set up before calling this method

voidenableRxJava2AssemblyTracking()

Start collecting filtered information about RxJava's execution to provide a more meaningful StackTrace in case of crash
Beware: Any crash-reporting handler should be set up before calling this method

voidenableRxJava2AssemblyTracking(@NullableString[] basePackageNames)

Disable the collection of more information about RxJava's execution
Information collected before calling this method will still be reported

voiddisableRxJava2AssemblyTracking()

Obtain a copy of the original Throwable with an extended StackTrace

@NullableThrowablegetEnhancedStackTrace(ThrowableoriginalException)

Features

StackTrace generation

Regardless of the thread a crash happens in, a nice StackTrace referring to the origin of the RxJava2 pipeline will be added to the causes of the crash.

Config:

RxJava2Debug.enableRxJava2AssemblyTracking();

Example (pointing to my own faulty code in com.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)):

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 22538java.lang.Throwable: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
athu.akarnokd.rxjava2.debug.ObservableOnAssembly$OnAssemblyObserver.onNext(ObservableOnAssembly.java:55)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
athu.akarnokd.rxjava2.debug.ObservableOnAssembly$OnAssemblyObserver.onNext(ObservableOnAssembly.java:55)
atio.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
atio.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:35)
atio.reactivex.Observable.subscribe(Observable.java:10838)
athu.akarnokd.rxjava2.debug.ObservableOnAssemblyScalarCallable.subscribeActual(ObservableOnAssemblyScalarCallable.java:41)
atio.reactivex.Observable.subscribe(Observable.java:10838)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
atio.reactivex.Scheduler$DisposeTask.run(Scheduler.java:452)
atio.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:61)
atio.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:52)
atjava.util.concurrent.FutureTask.run(FutureTask.java:237)
atjava.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
atjava.lang.Thread.run(Thread.java:761)
Causedby: causedbyjava.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atcom.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)
atandroid.app.Instrumentation.callActivityOnResume(Instrumentation.java:1269)
atandroid.app.Activity.performResume(Activity.java:6766)
atandroid.app.ActivityThread.performResumeActivity(ActivityThread.java:3377)
atandroid.app.ActivityThread.handleResumeActivity(ActivityThread.java:3440)
atandroid.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2713)
atandroid.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
atandroid.os.Handler.dispatchMessage(Handler.java:102)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)

Instead of the obscure:

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 27300io.reactivex.exceptions.OnErrorNotImplementedException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:704)
atio.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:701)
atio.reactivex.internal.observers.LambdaObserver.onError(LambdaObserver.java:74)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.checkTerminated(ObservableObserveOn.java:276)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:172)
atio.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:252)
atio.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:109)
atandroid.os.Handler.handleCallback(Handler.java:751)
atandroid.os.Handler.dispatchMessage(Handler.java:95)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atjava.lang.reflect.Method.invoke(NativeMethod)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
Causedby: java.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
atio.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
atio.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:35)
atio.reactivex.Observable.subscribe(Observable.java:10838)
atio.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
atio.reactivex.Scheduler$DisposeTask.run(Scheduler.java:452)
atio.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:61)
atio.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:52)
atjava.util.concurrent.FutureTask.run(FutureTask.java:237)
atjava.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
atjava.lang.Thread.run(Thread.java:761)

StackTrace filtering

Tell RxJava2Debug which packages names you want to highlight, and RxJava2Debug will make sure they appear as the top of the StackTrace

Config:

RxJava2Debug.enableRxJava2AssemblyTracking(newString[]{"com.akaita.fgas", "com.akaita.android"});

Result:

FATALEXCEPTION: mainProcess: com.akaita.fgas.debug, PID: 22538java.lang.Throwable: causedbyjava.lang.NullPointerException: Themapperfunctionreturnedanullvalue.
atio.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
atio.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
[...]
Causedby: java.lang.Throwableatcom.akaita.fgas.activities.TopActivity.onResume(TopActivity.java:205)
atandroid.app.Instrumentation.callActivityOnResume(Instrumentation.java:1269)
atandroid.app.Activity.performResume(Activity.java:6766)
atandroid.app.ActivityThread.performResumeActivity(ActivityThread.java:3377)
atandroid.app.ActivityThread.handleResumeActivity(ActivityThread.java:3440)
atandroid.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2713)
atandroid.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
atandroid.os.Handler.dispatchMessage(Handler.java:102)
atandroid.os.Looper.loop(Looper.java:154)
atandroid.app.ActivityThread.main(ActivityThread.java:6077)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)

This comes in very handy when you work with a error reporting system such as Crashlytics.

Instead of getting a multitude of reports involving RxJava2 into a single report entry, you'll get one entry per crash.

Credits

This library is using a subset of classes from akarnokd's RxJava2Extensions.

RxJava2Extensions takes about 1mb and contains 5231 methods. RxJava2Debug takes about 38kb and contains 239 methods.

About

RxJava 2.x extension to provide meaningful Stack Traces

Resources

Code of conduct

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages