Skip to content

Commit d968c1f

Browse files
committed
feat: Error handling enhancement for vector_graphics
- copy from this PR: dnfield/vector_graphics#258
1 parent aaa1b3f commit d968c1f

File tree

4 files changed

+69
-57
lines changed

4 files changed

+69
-57
lines changed

packages/vector_graphics/lib/src/listener.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ final Map<BytesLoader, Completer<void>> _pendingDecodes =
5555
/// Decode a vector graphics binary asset into a [Picture].
5656
///
5757
/// Throws a [StateError] if the data is invalid.
58-
Future<PictureInfo> decodeVectorGraphics(
58+
Future<PictureInfo?> decodeVectorGraphics(
5959
ByteData data, {
6060
required Locale? locale,
6161
required TextDirection? textDirection,

packages/vector_graphics/lib/src/vector_graphics.dart

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'dart:math' as math;
66
import 'dart:ui' as ui;
77

8+
import 'package:flutter/cupertino.dart';
89
import 'package:flutter/foundation.dart';
910
import 'package:flutter/widgets.dart';
1011
import 'package:vector_graphics_codec/vector_graphics_codec.dart';
@@ -279,8 +280,7 @@ class _PictureData {
279280

280281
@immutable
281282
class _PictureKey {
282-
const _PictureKey(
283-
this.cacheKey, this.locale, this.textDirection, this.clipViewbox);
283+
const _PictureKey(this.cacheKey, this.locale, this.textDirection, this.clipViewbox);
284284

285285
final Object cacheKey;
286286
final Locale? locale;
@@ -306,10 +306,8 @@ class _VectorGraphicWidgetState extends State<VectorGraphic> {
306306
Locale? locale;
307307
TextDirection? textDirection;
308308

309-
static final Map<_PictureKey, _PictureData> _livePictureCache =
310-
<_PictureKey, _PictureData>{};
311-
static final Map<_PictureKey, Future<_PictureData>> _pendingPictures =
312-
<_PictureKey, Future<_PictureData>>{};
309+
static final Map<_PictureKey, _PictureData?> _livePictureCache = <_PictureKey, _PictureData?>{};
310+
static final Map<_PictureKey, Future<_PictureData?>> _pendingPictures = <_PictureKey, Future<_PictureData?>>{};
313311

314312
@override
315313
void didChangeDependencies() {
@@ -345,29 +343,38 @@ class _VectorGraphicWidgetState extends State<VectorGraphic> {
345343
}
346344
}
347345

348-
Future<_PictureData> _loadPicture(
349-
BuildContext context, _PictureKey key, BytesLoader loader) {
346+
Future<_PictureData?> _loadPicture(BuildContext context, _PictureKey key, BytesLoader loader) {
350347
if (_pendingPictures.containsKey(key)) {
351348
return _pendingPictures[key]!;
352349
}
353-
final Future<_PictureData> result =
354-
loader.loadBytes(context).then((ByteData data) {
355-
return decodeVectorGraphics(
356-
data,
357-
locale: key.locale,
358-
textDirection: key.textDirection,
359-
clipViewbox: key.clipViewbox,
360-
loader: loader,
361-
onError: (Object error, StackTrace? stackTrace) {
362-
return _handleError(
363-
error,
364-
stackTrace,
365-
);
366-
},
367-
);
368-
}).then((PictureInfo pictureInfo) {
350+
351+
final Future<_PictureData?> result = loader.loadBytes(context).then((ByteData data) async {
352+
if (data.lengthInBytes == 0) {
353+
debugPrint('_VectorGraphicWidgetState.decodeVectorGraphics: empty');
354+
_handleError(const FormatException('Empty SVG xml content'), null);
355+
return null;
356+
} else {
357+
return decodeVectorGraphics(data,
358+
locale: key.locale,
359+
textDirection: key.textDirection,
360+
clipViewbox: key.clipViewbox,
361+
loader: loader, onError: (Object error, StackTrace? stackTrace) {
362+
debugPrintStack(
363+
stackTrace: stackTrace, label: '_VectorGraphicWidgetState.decodeVectorGraphics.onError: $error');
364+
_handleError(error, stackTrace);
365+
});
366+
}
367+
}).onError((Object? error, StackTrace stackTrace) {
368+
debugPrintStack(stackTrace: stackTrace, label: '_VectorGraphicWidgetState._loadPictureInfo.onError: $error');
369+
_handleError(error ?? '', stackTrace);
370+
return null;
371+
}).then((PictureInfo? pictureInfo) {
372+
if (pictureInfo == null) {
373+
return null;
374+
}
369375
return _PictureData(pictureInfo, 0, key);
370376
});
377+
371378
_pendingPictures[key] = result;
372379
result.whenComplete(() {
373380
_pendingPictures.remove(key);
@@ -376,6 +383,9 @@ class _VectorGraphicWidgetState extends State<VectorGraphic> {
376383
}
377384

378385
void _handleError(Object error, StackTrace? stackTrace) {
386+
if (!mounted) {
387+
return;
388+
}
379389
setState(() {
380390
_error = error;
381391
_stackTrace = stackTrace;
@@ -385,8 +395,7 @@ class _VectorGraphicWidgetState extends State<VectorGraphic> {
385395
void _loadAssetBytes() {
386396
// First check if we have an avilable picture and use this immediately.
387397
final Object loaderKey = widget.loader.cacheKey(context);
388-
final _PictureKey key =
389-
_PictureKey(loaderKey, locale, textDirection, widget.clipViewbox);
398+
final _PictureKey key = _PictureKey(loaderKey, locale, textDirection, widget.clipViewbox);
390399
final _PictureData? data = _livePictureCache[key];
391400
if (data != null) {
392401
data.count += 1;
@@ -398,7 +407,10 @@ class _VectorGraphicWidgetState extends State<VectorGraphic> {
398407
}
399408
// If not, then check if there is a pending load.
400409
final BytesLoader loader = widget.loader;
401-
_loadPicture(context, key, loader).then((_PictureData data) {
410+
_loadPicture(context, key, loader).then((_PictureData? data) {
411+
if (data == null) {
412+
return;
413+
}
402414
data.count += 1;
403415

404416
// The widget may have changed, requesting a new vector graphic before
@@ -674,7 +686,7 @@ class VectorGraphicUtilities {
674686
///
675687
/// It is the caller's responsibility to handle disposing the picture when
676688
/// they are done with it.
677-
Future<PictureInfo> loadPicture(
689+
Future<PictureInfo?> loadPicture(
678690
BytesLoader loader,
679691
BuildContext? context, {
680692
bool clipViewbox = true,

packages/vector_graphics/test/listener_test.dart

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,31 +43,31 @@ void main() {
4343
});
4444

4545
test('decode without clip', () async {
46-
final PictureInfo info = await decodeVectorGraphics(
46+
final PictureInfo? info = await decodeVectorGraphics(
4747
vectorGraphicBuffer,
4848
locale: ui.PlatformDispatcher.instance.locale,
4949
textDirection: ui.TextDirection.ltr,
5050
clipViewbox: true,
5151
loader: const AssetBytesLoader('test'),
5252
);
53-
final ui.Image image = info.picture.toImageSync(15, 15);
54-
final Uint32List imageBytes =
55-
(await image.toByteData())!.buffer.asUint32List();
53+
expect(info, isNotNull);
54+
final ui.Image image = info!.picture.toImageSync(15, 15);
55+
final Uint32List imageBytes = (await image.toByteData())!.buffer.asUint32List();
5656
expect(imageBytes.first, 0xFF000000);
5757
expect(imageBytes.last, 0x00000000);
5858
}, skip: kIsWeb);
5959

6060
test('decode with clip', () async {
61-
final PictureInfo info = await decodeVectorGraphics(
61+
final PictureInfo? info = await decodeVectorGraphics(
6262
vectorGraphicBuffer,
6363
locale: ui.PlatformDispatcher.instance.locale,
6464
textDirection: ui.TextDirection.ltr,
6565
clipViewbox: false,
6666
loader: const AssetBytesLoader('test'),
6767
);
68-
final ui.Image image = info.picture.toImageSync(15, 15);
69-
final Uint32List imageBytes =
70-
(await image.toByteData())!.buffer.asUint32List();
68+
expect(info, isNotNull);
69+
final ui.Image image = info!.picture.toImageSync(15, 15);
70+
final Uint32List imageBytes = (await image.toByteData())!.buffer.asUint32List();
7171
expect(imageBytes.first, 0xFF000000);
7272
expect(imageBytes.last, 0xFF000000);
7373
}, skip: kIsWeb);

packages/vector_graphics/test/render_vector_graphics_test.dart

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import 'package:vector_graphics/vector_graphics.dart';
1818
import 'package:vector_graphics_codec/vector_graphics_codec.dart';
1919

2020
void main() {
21-
late PictureInfo pictureInfo;
21+
late PictureInfo? pictureInfo;
2222

2323
tearDown(() {
2424
// Since we don't always explicitly dispose render objects in unit tests, manually clear
@@ -41,7 +41,7 @@ void main() {
4141

4242
test('Rasterizes a picture to a draw image call', () async {
4343
final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic(
44-
pictureInfo,
44+
pictureInfo!,
4545
'test',
4646
null,
4747
1.0,
@@ -62,15 +62,15 @@ void main() {
6262

6363
test('Multiple render objects with the same scale share a raster', () async {
6464
final RenderVectorGraphic renderVectorGraphicA = RenderVectorGraphic(
65-
pictureInfo,
65+
pictureInfo!,
6666
'test',
6767
null,
6868
1.0,
6969
null,
7070
1.0,
7171
);
7272
final RenderVectorGraphic renderVectorGraphicB = RenderVectorGraphic(
73-
pictureInfo,
73+
pictureInfo!,
7474
'test',
7575
null,
7676
1.0,
@@ -91,15 +91,15 @@ void main() {
9191

9292
test('disposing render object release raster', () async {
9393
final RenderVectorGraphic renderVectorGraphicA = RenderVectorGraphic(
94-
pictureInfo,
94+
pictureInfo!,
9595
'test',
9696
null,
9797
1.0,
9898
null,
9999
1.0,
100100
);
101101
final RenderVectorGraphic renderVectorGraphicB = RenderVectorGraphic(
102-
pictureInfo,
102+
pictureInfo!,
103103
'test',
104104
null,
105105
1.0,
@@ -126,15 +126,15 @@ void main() {
126126
'Multiple render objects with the same scale share a raster, different load order',
127127
() async {
128128
final RenderVectorGraphic renderVectorGraphicA = RenderVectorGraphic(
129-
pictureInfo,
129+
pictureInfo!,
130130
'test',
131131
null,
132132
1.0,
133133
null,
134134
1.0,
135135
);
136136
final RenderVectorGraphic renderVectorGraphicB = RenderVectorGraphic(
137-
pictureInfo,
137+
pictureInfo!,
138138
'test',
139139
null,
140140
1.0,
@@ -158,7 +158,7 @@ void main() {
158158

159159
test('Changing color filter does not re-rasterize', () async {
160160
final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic(
161-
pictureInfo,
161+
pictureInfo!,
162162
'test',
163163
null,
164164
1.0,
@@ -185,7 +185,7 @@ void main() {
185185
test('Changing device pixel ratio does re-rasterize and dispose old raster',
186186
() async {
187187
final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic(
188-
pictureInfo,
188+
pictureInfo!,
189189
'test',
190190
null,
191191
1.0,
@@ -210,7 +210,7 @@ void main() {
210210

211211
test('Changing scale does re-rasterize and dispose old raster', () async {
212212
final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic(
213-
pictureInfo,
213+
pictureInfo!,
214214
'test',
215215
null,
216216
1.0,
@@ -235,7 +235,7 @@ void main() {
235235

236236
test('The raster size is increased by the inverse picture scale', () async {
237237
final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic(
238-
pictureInfo,
238+
pictureInfo!,
239239
'test',
240240
null,
241241
1.0,
@@ -254,7 +254,7 @@ void main() {
254254

255255
test('The raster size is increased by the device pixel ratio', () async {
256256
final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic(
257-
pictureInfo,
257+
pictureInfo!,
258258
'test',
259259
null,
260260
2.0,
@@ -273,7 +273,7 @@ void main() {
273273
test('The raster size is increased by the device pixel ratio and ratio',
274274
() async {
275275
final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic(
276-
pictureInfo,
276+
pictureInfo!,
277277
'test',
278278
null,
279279
2.0,
@@ -292,7 +292,7 @@ void main() {
292292
test('Changing size asserts if it is different from the picture size',
293293
() async {
294294
final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic(
295-
pictureInfo,
295+
pictureInfo!,
296296
'test',
297297
null,
298298
1.0,
@@ -313,7 +313,7 @@ void main() {
313313
test('Does not rasterize a picture when fully transparent', () async {
314314
final FixedOpacityAnimation opacity = FixedOpacityAnimation(0.0);
315315
final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic(
316-
pictureInfo,
316+
pictureInfo!,
317317
'test',
318318
null,
319319
1.0,
@@ -339,7 +339,7 @@ void main() {
339339
test('paints partially opaque picture', () async {
340340
final FixedOpacityAnimation opacity = FixedOpacityAnimation(0.5);
341341
final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic(
342-
pictureInfo,
342+
pictureInfo!,
343343
'test',
344344
null,
345345
1.0,
@@ -355,7 +355,7 @@ void main() {
355355

356356
test('Disposing render object disposes picture', () async {
357357
final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic(
358-
pictureInfo,
358+
pictureInfo!,
359359
'test',
360360
null,
361361
1.0,
@@ -376,7 +376,7 @@ void main() {
376376
test('Removes listeners on detach, dispose, adds then on attach', () async {
377377
final FixedOpacityAnimation opacity = FixedOpacityAnimation(0.5);
378378
final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic(
379-
pictureInfo,
379+
pictureInfo!,
380380
'test',
381381
null,
382382
1.0,
@@ -412,7 +412,7 @@ void main() {
412412

413413
test('Color filter applies clip', () async {
414414
final RenderPictureVectorGraphic render = RenderPictureVectorGraphic(
415-
pictureInfo,
415+
pictureInfo!,
416416
const ui.ColorFilter.mode(Colors.green, ui.BlendMode.difference),
417417
null,
418418
);

0 commit comments

Comments
 (0)