Skip to content

Commit c4c217b

Browse files
fix: stacktrace in computed (#988)
* fix: preserve stacktrace in Computed and Reaction * tests: Add test cases for testing preserving stacktrace in Computed and Observer * chore: bump version to 2.3.1 and update changelog with bug fix for preserving stacktrace in Computed and Reaction functions --------- Co-authored-by: Pavan Podila <pavanpodila@users.noreply.github.com>
1 parent fdfb466 commit c4c217b

File tree

7 files changed

+96
-17
lines changed

7 files changed

+96
-17
lines changed

flutter_mobx/test/flutter_mobx_test.dart

+64-9
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,34 @@ void main() {
176176
expect(exception, isInstanceOf<MobXCaughtException>());
177177
});
178178

179+
testWidgets(
180+
'Observer should print full stacktrace of error coming from computed',
181+
(tester) async {
182+
late StackTrace stackTrace;
183+
final errorWrapper = await _testThrowingObserverWithStackTrace(
184+
tester,
185+
firstError: true,
186+
actionThrows: (tester) async {
187+
await tester.pumpWidget(
188+
Observer(
189+
builder: (context) {
190+
Computed(() {
191+
try {
192+
throw Exception();
193+
} on Exception catch (e, st) {
194+
stackTrace = st;
195+
rethrow;
196+
}
197+
}).value;
198+
},
199+
),
200+
);
201+
},
202+
);
203+
expect(errorWrapper.stackTrace, stackTrace);
204+
},
205+
);
206+
179207
testWidgets('Observer unmount should dispose Reaction', (tester) async {
180208
final mock = MockReaction();
181209
when(() => mock.hasObservables).thenReturn(true);
@@ -345,18 +373,45 @@ Future<MobXCaughtException> _testThrowingObserver(
345373
WidgetTester tester,
346374
Object errorToThrow,
347375
) async {
348-
late Object exception;
376+
return (await _testThrowingObserverWithStackTrace(
377+
tester,
378+
actionThrows: (tester) async {
379+
final count = Observable(0);
380+
await tester.pumpWidget(FlutterErrorThrowingObserver(
381+
errorToThrow: errorToThrow,
382+
builder: (context) => Text(count.value.toString()),
383+
));
384+
count.value++;
385+
},
386+
))
387+
.exception as MobXCaughtException;
388+
}
389+
390+
class _ErrorWrapper {
391+
final Object exception;
392+
final StackTrace? stackTrace;
393+
394+
_ErrorWrapper({required this.exception, required this.stackTrace});
395+
}
396+
397+
Future<_ErrorWrapper> _testThrowingObserverWithStackTrace(
398+
WidgetTester tester, {
399+
required Future<void> Function(WidgetTester tester) actionThrows,
400+
bool firstError = false,
401+
}) async {
402+
_ErrorWrapper? errorWrapper;
349403
final prevOnError = FlutterError.onError;
350-
FlutterError.onError = (details) => exception = details.exception;
404+
FlutterError.onError = (details) {
405+
if (firstError && errorWrapper != null) {
406+
return;
407+
}
408+
errorWrapper =
409+
_ErrorWrapper(exception: details.exception, stackTrace: details.stack);
410+
};
351411

352412
try {
353-
final count = Observable(0);
354-
await tester.pumpWidget(FlutterErrorThrowingObserver(
355-
errorToThrow: errorToThrow,
356-
builder: (context) => Text(count.value.toString()),
357-
));
358-
count.value++;
359-
return exception as MobXCaughtException;
413+
await actionThrows(tester);
414+
return errorWrapper!;
360415
} finally {
361416
FlutterError.onError = prevOnError;
362417
}

mobx/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.3.1
2+
3+
- Fix preserving stacktrace in Computed and Reaction when exception thrown inside argument function
4+
15
## 2.3.0+1
26

37
- `pubspec.yaml` updated to include homepage and topics

mobx/lib/src/core/computed.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ class Computed<T> extends Atom implements Derivation, ObservableValue<T> {
9898
}
9999

100100
if (_context._hasCaughtException(this)) {
101-
throw _errorValue!;
101+
Error.throwWithStackTrace(_errorValue!, _errorValue!._stackTrace);
102102
}
103103

104104
return _value as T;

mobx/lib/src/core/reaction.dart

+8-5
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ class ReactionImpl with DebugCreationStack implements Reaction {
9191
}
9292

9393
if (_context._hasCaughtException(this)) {
94-
_reportException(_errorValue!);
94+
_reportException(_errorValue!, _errorValue!.stackTrace);
9595
}
9696

9797
if (notify) {
@@ -120,7 +120,7 @@ class ReactionImpl with DebugCreationStack implements Reaction {
120120
} on Object catch (e, s) {
121121
// Note: "on Object" accounts for both Error and Exception
122122
_errorValue = MobXCaughtException(e, stackTrace: s);
123-
_reportException(_errorValue!);
123+
_reportException(_errorValue!, s);
124124
}
125125
}
126126

@@ -165,15 +165,18 @@ class ReactionImpl with DebugCreationStack implements Reaction {
165165
// Not applicable right now
166166
}
167167

168-
void _reportException(Object exception) {
168+
void _reportException(Object exception, StackTrace? stackTrace) {
169169
if (_onError != null) {
170170
_onError!(exception, this);
171171
return;
172172
}
173173

174174
if (_context.config.disableErrorBoundaries == true) {
175-
// ignore: only_throw_errors
176-
throw exception;
175+
if (stackTrace != null) {
176+
Error.throwWithStackTrace(exception, stackTrace);
177+
} else {
178+
throw exception;
179+
}
177180
}
178181

179182
if (_context.isSpyEnabled) {

mobx/lib/version.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
// Generated via set_version.dart. !!!DO NOT MODIFY BY HAND!!!
22

33
/// The current version as per `pubspec.yaml`.
4-
const version = '2.3.0+1';
4+
const version = '2.3.1';

mobx/pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: mobx
2-
version: 2.3.0+1
2+
version: 2.3.1
33
description: "MobX is a library for reactively managing the state of your applications. Use the power of observables, actions, and reactions to supercharge your Dart and Flutter apps."
44

55
repository: https://github.com/mobxjs/mobx.dart

mobx/test/exceptions_test.dart

+17
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,21 @@ void main() {
2626
expect(ex.stackTrace, isNotNull);
2727
}
2828
});
29+
30+
test('should preserve stacktrace', () async {
31+
late StackTrace stackTrace;
32+
try {
33+
Computed(() {
34+
try {
35+
throw Exception();
36+
} on Exception catch (e, st) {
37+
stackTrace = st;
38+
rethrow;
39+
}
40+
}).value;
41+
} on MobXCaughtException catch (e, st) {
42+
expect(st, stackTrace);
43+
expect(st, e.stackTrace);
44+
}
45+
});
2946
}

0 commit comments

Comments
 (0)