Skip to content

Commit c67eafa

Browse files
lidongze91Ben Li
andauthored
Add native stacktrace field for PlatformException (#63502)
* Add native stacktrace field for PlatformException * Mute the readValue check for stacktrace. * polish * Add unit test and further polish * Added more comments * remove unnecessary import * fill in stacktrace to JSONMethodCodec and fix tests * fix style * Fix format * Remove unnecessary TODO since not all explicitly declared errors have stacktrace in it from native side. * Handle case for exception without stacktrace on jsonMethodCodec * Add more unit tests * format test Co-authored-by: Ben Li <libe@google.com>
1 parent fcbee10 commit c67eafa

4 files changed

Lines changed: 112 additions & 13 deletions

File tree

packages/flutter/lib/src/services/message_codec.dart

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,10 @@ abstract class MethodCodec {
8080

8181
/// Encodes an error result into a binary envelope.
8282
///
83-
/// The specified error [code], human-readable error [message], and error
84-
/// [details] correspond to the fields of [PlatformException].
85-
ByteData encodeErrorEnvelope({ required String code, String? message, dynamic details });
83+
/// The specified error [code], human-readable error [message], error
84+
/// [details] correspond to the fields of [PlatformException] and error
85+
/// [stacktrace] correspond to stacktrace from native platforms.
86+
ByteData encodeErrorEnvelope({ required String code, String? message, dynamic details, String? stacktrace});
8687
}
8788

8889

@@ -107,6 +108,7 @@ class PlatformException implements Exception {
107108
required this.code,
108109
this.message,
109110
this.details,
111+
this.stacktrace,
110112
}) : assert(code != null);
111113

112114
/// An error code.
@@ -118,8 +120,18 @@ class PlatformException implements Exception {
118120
/// Error details, possibly null.
119121
final dynamic details;
120122

123+
/// Native stacktrace for the error, possibly null.
124+
/// This is strictly for native platform stacktrace.
125+
/// The stacktrace info on dart platform can be found within the try-catch block for example:
126+
/// try {
127+
/// ...
128+
/// } catch (e, stacktrace) {
129+
/// print(stacktrace);
130+
/// }
131+
final String? stacktrace;
132+
121133
@override
122-
String toString() => 'PlatformException($code, $message, $details)';
134+
String toString() => 'PlatformException($code, $message, $details, $stacktrace)';
123135
}
124136

125137
/// Thrown to indicate that a platform interaction failed to find a handling

packages/flutter/lib/src/services/message_codecs.dart

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,16 @@ class JSONMethodCodec implements MethodCodec {
152152
message: decoded[1] as String,
153153
details: decoded[2],
154154
);
155+
if (decoded.length == 4
156+
&& decoded[0] is String
157+
&& (decoded[1] == null || decoded[1] is String)
158+
&& (decoded[3] == null || decoded[3] is String))
159+
throw PlatformException(
160+
code: decoded[0] as String,
161+
message: decoded[1] as String,
162+
details: decoded[2],
163+
stacktrace: decoded[3] as String,
164+
);
155165
throw FormatException('Invalid envelope: $decoded');
156166
}
157167

@@ -161,9 +171,9 @@ class JSONMethodCodec implements MethodCodec {
161171
}
162172

163173
@override
164-
ByteData encodeErrorEnvelope({ required String code, String? message, dynamic details }) {
174+
ByteData encodeErrorEnvelope({ required String code, String? message, dynamic details, String? stacktrace}) {
165175
assert(code != null);
166-
return const JSONMessageCodec().encodeMessage(<dynamic>[code, message, details])!;
176+
return const JSONMessageCodec().encodeMessage(<dynamic>[code, message, details, stacktrace])!;
167177
}
168178
}
169179

@@ -547,12 +557,13 @@ class StandardMethodCodec implements MethodCodec {
547557
}
548558

549559
@override
550-
ByteData encodeErrorEnvelope({ required String code, String? message, dynamic details }) {
560+
ByteData encodeErrorEnvelope({ required String code, String? message, dynamic details, String? stacktrace}) {
551561
final WriteBuffer buffer = WriteBuffer();
552562
buffer.putUint8(1);
553563
messageCodec.writeValue(buffer, code);
554564
messageCodec.writeValue(buffer, message);
555565
messageCodec.writeValue(buffer, details);
566+
messageCodec.writeValue(buffer, stacktrace);
556567
return buffer.done();
557568
}
558569

@@ -567,8 +578,9 @@ class StandardMethodCodec implements MethodCodec {
567578
final dynamic errorCode = messageCodec.readValue(buffer);
568579
final dynamic errorMessage = messageCodec.readValue(buffer);
569580
final dynamic errorDetails = messageCodec.readValue(buffer);
581+
final String? errorStacktrace = (buffer.hasRemaining) ? messageCodec.readValue(buffer) as String : null;
570582
if (errorCode is String && (errorMessage == null || errorMessage is String) && !buffer.hasRemaining)
571-
throw PlatformException(code: errorCode, message: errorMessage as String, details: errorDetails);
583+
throw PlatformException(code: errorCode, message: errorMessage as String, details: errorDetails, stacktrace: errorStacktrace);
572584
else
573585
throw const FormatException('Invalid envelope');
574586
}

packages/flutter/test/services/message_codecs_test.dart

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import 'dart:typed_data';
1111

1212
import 'package:flutter/services.dart';
13+
import 'package:matcher/matcher.dart';
1314
import '../flutter_test_alternative.dart';
1415
import 'message_codecs_testing.dart';
1516

@@ -36,14 +37,72 @@ void main() {
3637
final ByteData helloByteData = string.encodeMessage('hello');
3738

3839
final ByteData offsetByteData = ByteData.view(
39-
helloWorldByteData.buffer,
40-
helloByteData.lengthInBytes,
41-
helloWorldByteData.lengthInBytes - helloByteData.lengthInBytes,
40+
helloWorldByteData.buffer,
41+
helloByteData.lengthInBytes,
42+
helloWorldByteData.lengthInBytes - helloByteData.lengthInBytes,
4243
);
4344

4445
expect(string.decodeMessage(offsetByteData), ' world');
4546
});
4647
});
48+
group('Standard method codec', () {
49+
const MethodCodec method = StandardMethodCodec();
50+
test('should decode error envelope without native stacktrace', () {
51+
final ByteData errorData = method.encodeErrorEnvelope(
52+
code: 'errorCode',
53+
message: 'errorMessage',
54+
details: 'errorDetails',
55+
);
56+
expect(
57+
() => method.decodeEnvelope(errorData),
58+
throwsA(predicate((PlatformException e) =>
59+
e is PlatformException &&
60+
e.code == 'errorCode' &&
61+
e.message == 'errorMessage' &&
62+
e.details == 'errorDetails')));
63+
});
64+
test('should decode error envelope with native stacktrace.', () {
65+
final ByteData errorData = method.encodeErrorEnvelope(
66+
code: 'errorCode',
67+
message: 'errorMessage',
68+
details: 'errorDetails',
69+
stacktrace: 'errorStacktrace',
70+
);
71+
expect(
72+
() => method.decodeEnvelope(errorData),
73+
throwsA(predicate((PlatformException e) =>
74+
e is PlatformException && e.stacktrace == 'errorStacktrace')));
75+
});
76+
});
77+
group('Json method codec', () {
78+
const JSONMethodCodec json = JSONMethodCodec();
79+
test('should decode error envelope without native stacktrace', () {
80+
final ByteData errorData = json.encodeErrorEnvelope(
81+
code: 'errorCode',
82+
message: 'errorMessage',
83+
details: 'errorDetails',
84+
);
85+
expect(
86+
() => json.decodeEnvelope(errorData),
87+
throwsA(predicate((PlatformException e) =>
88+
e is PlatformException &&
89+
e.code == 'errorCode' &&
90+
e.message == 'errorMessage' &&
91+
e.details == 'errorDetails')));
92+
});
93+
test('should decode error envelope with native stacktrace.', () {
94+
final ByteData errorData = json.encodeErrorEnvelope(
95+
code: 'errorCode',
96+
message: 'errorMessage',
97+
details: 'errorDetails',
98+
stacktrace: 'errorStacktrace',
99+
);
100+
expect(
101+
() => json.decodeEnvelope(errorData),
102+
throwsA(predicate((PlatformException e) =>
103+
e is PlatformException && e.stacktrace == 'errorStacktrace')));
104+
});
105+
});
47106
group('JSON message codec', () {
48107
const MessageCodec<dynamic> json = JSONMessageCodec();
49108
test('should encode and decode simple messages', () {
@@ -151,8 +210,22 @@ void main() {
151210
standard,
152211
1.0,
153212
<int>[
154-
6, 0, 0, 0, 0, 0, 0, 0,
155-
0, 0, 0, 0, 0, 0, 0xf0, 0x3f,
213+
6,
214+
0,
215+
0,
216+
0,
217+
0,
218+
0,
219+
0,
220+
0,
221+
0,
222+
0,
223+
0,
224+
0,
225+
0,
226+
0,
227+
0xf0,
228+
0x3f,
156229
],
157230
);
158231
});

packages/flutter/test/services/platform_channel_test.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ void main() {
131131
'bad',
132132
'Something happened',
133133
<String, dynamic>{'a': 42, 'b': 3.14},
134+
'errorStacktrace',
134135
]);
135136
},
136137
);
@@ -141,6 +142,7 @@ void main() {
141142
expect(e.code, equals('bad'));
142143
expect(e.message, equals('Something happened'));
143144
expect(e.details, equals(<String, dynamic>{'a': 42, 'b': 3.14}));
145+
expect(e.stacktrace, equals('errorStacktrace'));
144146
} catch (e) {
145147
fail('PlatformException expected');
146148
}

0 commit comments

Comments
 (0)