TextPainter should dispatch creation and disposal events. (#137416)
diff --git a/packages/flutter/lib/src/material/snack_bar.dart b/packages/flutter/lib/src/material/snack_bar.dart
index 85df795..520abc2 100644
--- a/packages/flutter/lib/src/material/snack_bar.dart
+++ b/packages/flutter/lib/src/material/snack_bar.dart
@@ -664,6 +664,7 @@
final double actionAndIconWidth = actionTextPainter.size.width +
(widget.action != null ? actionHorizontalMargin : 0) +
(showCloseIcon ? (iconButton?.iconSize ?? 0 + iconHorizontalMargin) : 0);
+ actionTextPainter.dispose();
final EdgeInsets margin = widget.margin?.resolve(TextDirection.ltr) ?? snackBarTheme.insetPadding ?? defaults.insetPadding!;
diff --git a/packages/flutter/lib/src/material/switch.dart b/packages/flutter/lib/src/material/switch.dart
index 975bbb5..7bef0d2 100644
--- a/packages/flutter/lib/src/material/switch.dart
+++ b/packages/flutter/lib/src/material/switch.dart
@@ -1309,6 +1309,7 @@
notifyListeners();
}
+ final TextPainter _textPainter = TextPainter();
Color? _cachedThumbColor;
ImageProvider? _cachedThumbImage;
ImageErrorListener? _cachedThumbErrorListener;
@@ -1594,16 +1595,15 @@
shadows: iconShadows,
),
);
- final TextPainter textPainter = TextPainter(
- textDirection: textDirection,
- text: textSpan,
- );
- textPainter.layout();
+ _textPainter
+ ..textDirection = textDirection
+ ..text = textSpan;
+ _textPainter.layout();
final double additionalHorizontalOffset = (thumbSize.width - iconSize) / 2;
final double additionalVerticalOffset = (thumbSize.height - iconSize) / 2;
final Offset offset = thumbPaintOffset + Offset(additionalHorizontalOffset, additionalVerticalOffset);
- textPainter.paint(canvas, offset);
+ _textPainter.paint(canvas, offset);
}
} finally {
_isPainting = false;
@@ -1612,6 +1612,7 @@
@override
void dispose() {
+ _textPainter.dispose();
_cachedThumbPainter?.dispose();
_cachedThumbPainter = null;
_cachedThumbColor = null;
diff --git a/packages/flutter/lib/src/painting/flutter_logo.dart b/packages/flutter/lib/src/painting/flutter_logo.dart
index 81f08ee..ecbdffd 100644
--- a/packages/flutter/lib/src/painting/flutter_logo.dart
+++ b/packages/flutter/lib/src/painting/flutter_logo.dart
@@ -215,10 +215,15 @@
final FlutterLogoDecoration _config;
// these are configured assuming a font size of 100.0.
- // TODO(dnfield): Figure out how to dispose this https://github.com/flutter/flutter/issues/110601
late TextPainter _textPainter;
late Rect _textBoundingRect;
+ @override
+ void dispose() {
+ _textPainter.dispose();
+ super.dispose();
+ }
+
void _prepareText() {
const String kLabel = 'Flutter';
_textPainter = TextPainter(
diff --git a/packages/flutter/lib/src/painting/text_painter.dart b/packages/flutter/lib/src/painting/text_painter.dart
index 955e716..e8a4d64 100644
--- a/packages/flutter/lib/src/painting/text_painter.dart
+++ b/packages/flutter/lib/src/painting/text_painter.dart
@@ -438,6 +438,8 @@
final double lineVerticalOffset;
}
+const String _flutterPaintingLibrary = 'package:flutter/painting.dart';
+
/// An object that paints a [TextSpan] tree into a [Canvas].
///
/// To use a [TextPainter], follow these steps:
@@ -498,7 +500,17 @@
_locale = locale,
_strutStyle = strutStyle,
_textWidthBasis = textWidthBasis,
- _textHeightBehavior = textHeightBehavior;
+ _textHeightBehavior = textHeightBehavior {
+ // TODO(polina-c): stop duplicating code across disposables
+ // https://github.com/flutter/flutter/issues/137435
+ if (kFlutterMemoryAllocationsEnabled) {
+ MemoryAllocations.instance.dispatchObjectCreated(
+ library: _flutterPaintingLibrary,
+ className: '$TextPainter',
+ object: this,
+ );
+ }
+ }
/// Computes the width of a configured [TextPainter].
///
@@ -1598,6 +1610,11 @@
_disposed = true;
return true;
}());
+ // TODO(polina-c): stop duplicating code across disposables
+ // https://github.com/flutter/flutter/issues/137435
+ if (kFlutterMemoryAllocationsEnabled) {
+ MemoryAllocations.instance.dispatchObjectDisposed(object: this);
+ }
_layoutTemplate?.dispose();
_layoutTemplate = null;
_layoutCache?.paragraph.dispose();
diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart
index c6f1f0c..fdee268 100644
--- a/packages/flutter/lib/src/rendering/proxy_box.dart
+++ b/packages/flutter/lib/src/rendering/proxy_box.dart
@@ -2264,6 +2264,12 @@
}
@override
+ void dispose() {
+ _painter?.dispose();
+ super.dispose();
+ }
+
+ @override
bool hitTestSelf(Offset position) {
return _decoration.hitTest(size, position, textDirection: configuration.textDirection);
}
diff --git a/packages/flutter/lib/src/widgets/banner.dart b/packages/flutter/lib/src/widgets/banner.dart
index 33a11fd..d111839 100644
--- a/packages/flutter/lib/src/widgets/banner.dart
+++ b/packages/flutter/lib/src/widgets/banner.dart
@@ -231,7 +231,7 @@
///
/// * [CheckedModeBanner], which the [WidgetsApp] widget includes by default in
/// debug mode, to show a banner that says "DEBUG".
-class Banner extends StatelessWidget {
+class Banner extends StatefulWidget {
/// Creates a banner.
const Banner({
super.key,
@@ -289,30 +289,47 @@
final TextStyle textStyle;
@override
+ State<Banner> createState() => _BannerState();
+}
+
+class _BannerState extends State<Banner> {
+ BannerPainter? _painter;
+
+ @override
+ void dispose() {
+ _painter?.dispose();
+ super.dispose();
+ }
+
+ @override
Widget build(BuildContext context) {
- assert((textDirection != null && layoutDirection != null) || debugCheckHasDirectionality(context));
+ assert((widget.textDirection != null && widget.layoutDirection != null) || debugCheckHasDirectionality(context));
+
+ _painter?.dispose();
+ _painter = BannerPainter(
+ message: widget.message,
+ textDirection: widget.textDirection ?? Directionality.of(context),
+ location: widget.location,
+ layoutDirection: widget.layoutDirection ?? Directionality.of(context),
+ color: widget.color,
+ textStyle: widget.textStyle,
+ );
+
return CustomPaint(
- foregroundPainter: BannerPainter(
- message: message,
- textDirection: textDirection ?? Directionality.of(context),
- location: location,
- layoutDirection: layoutDirection ?? Directionality.of(context),
- color: color,
- textStyle: textStyle,
- ),
- child: child,
+ foregroundPainter: _painter,
+ child: widget.child,
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
- properties.add(StringProperty('message', message, showName: false));
- properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
- properties.add(EnumProperty<BannerLocation>('location', location));
- properties.add(EnumProperty<TextDirection>('layoutDirection', layoutDirection, defaultValue: null));
- properties.add(ColorProperty('color', color, showName: false));
- textStyle.debugFillProperties(properties, prefix: 'text ');
+ properties.add(StringProperty('message', widget.message, showName: false));
+ properties.add(EnumProperty<TextDirection>('textDirection', widget.textDirection, defaultValue: null));
+ properties.add(EnumProperty<BannerLocation>('location', widget.location));
+ properties.add(EnumProperty<TextDirection>('layoutDirection', widget.layoutDirection, defaultValue: null));
+ properties.add(ColorProperty('color', widget.color, showName: false));
+ widget.textStyle.debugFillProperties(properties, prefix: 'text ');
}
}
diff --git a/packages/flutter/test/painting/text_painter_test.dart b/packages/flutter/test/painting/text_painter_test.dart
index 8dec069..a3ce3c1 100644
--- a/packages/flutter/test/painting/text_painter_test.dart
+++ b/packages/flutter/test/painting/text_painter_test.dart
@@ -7,6 +7,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
+import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
void _checkCaretOffsetsLtrAt(String text, List<int> boundaries) {
expect(boundaries.first, 0);
@@ -1525,6 +1526,13 @@
expect(metrics, hasLength(1));
}
}, skip: kIsWeb && !isCanvasKit); // [intended] Browsers seem to always round font/glyph metrics.
+
+ test('TextPainter dispatches memory events', () async {
+ await expectLater(
+ await memoryEvents(() => TextPainter().dispose(), TextPainter),
+ areCreateAndDispose,
+ );
+ });
}
class MockCanvas extends Fake implements Canvas {