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 {