[flutter] Guard async SpineWidget loads after dispose

Closes #3038
This commit is contained in:
Mario Zechner 2026-03-14 15:58:33 +01:00
parent e5bc9b709e
commit 516518b2d9

View File

@ -438,15 +438,12 @@ class SpineWidget extends StatefulWidget {
class _SpineWidgetState extends State<SpineWidget> { class _SpineWidgetState extends State<SpineWidget> {
late Bounds _computedBounds; late Bounds _computedBounds;
SkeletonDrawableFlutter? _drawable; SkeletonDrawableFlutter? _drawable;
int _loadGeneration = 0;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
if (widget._assetType == _AssetType.drawable) { _loadCurrentDrawable();
loadDrawable(widget._drawable!);
} else {
loadFromAsset(widget._bundle, widget._atlasFile!, widget._skeletonFile!, widget._assetType);
}
} }
@override @override
@ -469,16 +466,29 @@ class _SpineWidgetState extends State<SpineWidget> {
} }
if (hasChanged) { if (hasChanged) {
widget._controller._drawable?.dispose(); _disposeDrawable(oldWidget._controller);
_drawable = null; _loadCurrentDrawable();
if (widget._assetType == _AssetType.drawable) {
loadDrawable(widget._drawable!);
} else {
loadFromAsset(widget._bundle, widget._atlasFile!, widget._skeletonFile!, widget._assetType);
}
} }
} }
void _loadCurrentDrawable() {
final loadGeneration = ++_loadGeneration;
if (widget._assetType == _AssetType.drawable) {
loadDrawable(widget._drawable!);
} else {
loadFromAsset(widget._bundle, widget._atlasFile!, widget._skeletonFile!, widget._assetType, loadGeneration);
}
}
void _disposeDrawable(SpineWidgetController controller) {
final drawable = _drawable;
if (drawable == null) return;
if (controller._drawable == drawable) controller._drawable = null;
drawable.dispose();
_drawable = null;
}
void loadDrawable(SkeletonDrawableFlutter drawable) { void loadDrawable(SkeletonDrawableFlutter drawable) {
_drawable = drawable; _drawable = drawable;
_computedBounds = widget._boundsProvider.computeBounds(drawable); _computedBounds = widget._boundsProvider.computeBounds(drawable);
@ -486,23 +496,37 @@ class _SpineWidgetState extends State<SpineWidget> {
setState(() {}); setState(() {});
} }
void loadFromAsset(AssetBundle? bundle, String atlasFile, String skeletonFile, _AssetType assetType) async { void loadFromAsset(
AssetBundle? bundle,
String atlasFile,
String skeletonFile,
_AssetType assetType,
int loadGeneration,
) async {
late final SkeletonDrawableFlutter drawable;
switch (assetType) { switch (assetType) {
case _AssetType.asset: case _AssetType.asset:
loadDrawable(await SkeletonDrawableFlutter.fromAsset(atlasFile, skeletonFile, bundle: bundle)); drawable = await SkeletonDrawableFlutter.fromAsset(atlasFile, skeletonFile, bundle: bundle);
break; break;
case _AssetType.file: case _AssetType.file:
loadDrawable(await SkeletonDrawableFlutter.fromFile(atlasFile, skeletonFile)); drawable = await SkeletonDrawableFlutter.fromFile(atlasFile, skeletonFile);
break; break;
case _AssetType.http: case _AssetType.http:
loadDrawable(await SkeletonDrawableFlutter.fromHttp(atlasFile, skeletonFile)); drawable = await SkeletonDrawableFlutter.fromHttp(atlasFile, skeletonFile);
break; break;
case _AssetType.memory: case _AssetType.memory:
loadDrawable(await SkeletonDrawableFlutter.fromMemory(atlasFile, skeletonFile, widget._loadFile!)); drawable = await SkeletonDrawableFlutter.fromMemory(atlasFile, skeletonFile, widget._loadFile!);
break; break;
case _AssetType.drawable: case _AssetType.drawable:
throw Exception("Drawable can not be loaded via loadFromAsset()."); throw Exception("Drawable can not be loaded via loadFromAsset().");
} }
if (!mounted || loadGeneration != _loadGeneration) {
drawable.dispose();
return;
}
loadDrawable(drawable);
} }
@override @override
@ -523,8 +547,9 @@ class _SpineWidgetState extends State<SpineWidget> {
@override @override
void dispose() { void dispose() {
_loadGeneration++;
_disposeDrawable(widget._controller);
super.dispose(); super.dispose();
widget._controller._drawable?.dispose();
} }
} }