Question about repaints and rendering algorithms

Hello everyone,

I am trying to learn some more details about Flutter’s performance and rendering and have a question about the debug repaint rainbow.

When I create a very simple stateful widget with only a dynamic Text-Widget and wrap a RepaintBoundary around it, why does flutter repaint the whole app so often? When I run the following code, the outer rectangle ist also changing very often. When I replace Text('$value') with a CircularProgressIndicator() it does not.

My example code is as follows (tested on Linux with Flutter 3.27.1):

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() {
  debugRepaintRainbowEnabled = true;
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(body: RepaintBoundary(child: SubWidget())),
    );
  }
}

class SubWidget extends StatefulWidget {
  @override
  State<SubWidget> createState() => _SubWidgetState();
}

class _SubWidgetState extends State<SubWidget> {
  int value = 0;

  @override
  void initState() {
    super.initState();

    Timer.periodic(const Duration(milliseconds: 5), (_) {
      ++value;
      setState(() {});
    });
  }

  // Many repaints
  @override
  Widget build(BuildContext context) => Text('$value');

  // No repaints
  // @override
  // Widget build(BuildContext context) => CircularProgressIndicator();
}

Thank you in advance for your help.

1 Like

I think that what’s happening is that your Subwidget changes size every 5 milliseconds, and so Flutter must re-layout. Basically, it can’t prove that the change is self-contained to SubWidget.

Try putting the Text in something like SizedBox with enough room.

CircularProgressIndicator doesn’t change size while it’s animating, so Flutter is happy to contain the repaint to just that part of your widget tree.

1 Like

That’s it, thank you! It makes sense to me now. The oversimplification of my example created the problem in the first place.

Repainting is a measurement of the Flutter ui workload and doesn’t actually have any direct impact on “rendering” (in terms of raster thread or GPU workload) performance.

A repaint rainbow means “All render objects in this area called their paint method” and nothing more. If there aren’t very many ROs (because the example is trivial) then it doesn’t indicate any wasted work. For example, If you had a very complex page in your application with lots of nested widgets, you could use it to check that a small animation didn’t cause you to repaint the entire page.

2 Likes

Would it help here to introduce a RepaintBoundary, or is that solving a different problem?

I already used a RepaintBoundary in my example above the SubWidget.

What I didn’t understand was that the text constantly changes size (which is actually obvious) and thus makes relayouts necessary, as @filip explained. Putting a SizedBox above it prevents the re-layouts.

I would like to briefly note again that I did not want to solve a real problem with my question, but to better understand Flutter. Many thanks to @jonahwilliams for the further details!