Repository navigation
Defining updating theme causes lot of rebuilds #89127
Description
If you define textButtonTheme with TextButton.styleFrom in the MaterialApp's theme property, there will be a lot of unnecessary (?) rebuilds.
Also Theme.of(context) must be used to reproduce this bug.
You can see on the attached pictures that adding TextButton.styleFrom(primary: Colors.red), will cause 12 times more rebuilds than with the code without it.
If I remove Theme.of(context) from the Scaffold then it only gets rebuilt once.
note: I forced a rebuild with cmd+s (hot reload). In the actual application, I'm using a BlocBuilder and this causes the initial rebuild.
I know that flutter can call rebuild whenever it wants and I should calculate with it but rebuilding it 12 times is obviously noticeable (there is a little flickering in the elements in my actual app)
Steps to Reproduce
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(),
)),
debugShowCheckedModeBanner: false,
home: Home());
}
}
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('Home build');
return Scaffold(
backgroundColor: Theme.of(context).primaryColor,
);
}
}
channel: stable
version: 2.2.3
macOS 11.5.2
simulator: iPhone 8 (iOS 14.5)
Expected results:
Actual results:
Logs
info • Avoid `print` calls in production code • example/lib/main.dart:21:5 • avoid_print
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.2.3, on macOS 11.5.2 20G95 darwin-x64, locale en-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[✓] Android Studio (version 3.5)
[✓] VS Code (version 1.59.1)
[✓] Connected device (2 available)
• No issues found!


Activity
danagbemava-nc commentedon Aug 30, 2021
Hi @hbali, thanks for filing the issue.
Using
hot-reloadto force rebuild the app I was able to confirm your results, however, I wanted to know if this had anything to do with state rebuilds so I modified your sample code a little but after the modification I'm unable to reproduce it.Perhaps this could be a different issue?
Kindly try out my example to confirm for yourself.
updated code sample
logs(timer was running in the background)
`Notice the home build prints only once after each ticker print`hbali commentedon Aug 30, 2021
@danagbemava-nc
Thank you for your response!
I copied your code and the result is the same as yours, build only happens once per
setState.I modified the code a bit and moved the rebuild outside the Home widget (so the
MaterialAppalso gets rebuilt). This is closer to my actual app implementation since the issue occurs when I change the color theme of the app → thus I need to rebuild the wholeMaterialAppThis way, the problem occurs again.
Logs
hbali commentedon Aug 30, 2021
After some more investigation:
the same problem occurs if I define
cupertinoOverrideThemein theThemeData. I wanted to change the default purple color of theCupertinoActionSheettext (why is it purple, why isn't itsystemBlue?) and numerous rebuilds happened as soon as I defined the property.Probably there are more properties in
ThemeDatathat causes these mysterious rebuildsdanagbemava-nc commentedon Aug 30, 2021
I can reproduce this issue on
masterandstablewith the source in this comment.flutter doctor -v
log.txt
[-]Defining textButtonTheme (with style: TextButton.styleFrom) in the app's theme causes a lot of rebuilds[/-][+]Defining updating theme causes lot of rebuilds[/+]TahaTesser commentedon Mar 24, 2022
This is not just
TextButtonThemebut any theme updates cause a lot of rebuilds. I think this is expected since the theme contains many visual changes, any modifications to the theme would require app rebuilds.minimal code sample
Results
Keeping the issue open in case anyone face problems
10 remaining items
raghavendra-k-j commentedon Apr 6, 2024
I think the reason behind this is lerp methods which makes the theme changing animations smoother

rydmike commentedon Apr 6, 2024
@raghavendra-k-j yes it is a correct observation that all values in
ThemeDataare changed via alerpanimation. So if you observe the rebuild of widgets that are affected by any changed property value inThemeData, you will see a lot of rebuilds of those widgets as theThemelinearly animates through those values to the new target value. This is normal and expected behavior.However, the problem is that as soon as you use any component theme that contains a
MaterialStateProperty(now in masterWidgetStateProperty) that is not null, ie defined to some custom theme value, theThemewill lerp animate and rebuild all widgets, even if there is no value change onThemeData.This happens because using
MaterialStatePropertybreaks object equality, so the sameThemeDatais no longer equal and everything is animated and rebuilt anyway, even if nothing was changed and nothing should have been rebuilt.This is imo a real and severe issue with
ThemeDataand the usedMaterialStatePropertysolution.Another sample to demo the issue
Consider this "a bit" more involved sample:
On first build we see this:
We see the 3 builds that the framework triggers (I wish that was just once too, but there are bunch of wrappers that triggers it 3 times).
Then many Scaffold rebuilds as the theme change was animated. But was there really something to lerp animate? Let's look at it in more detail.
Elevate AppBar
Let's start by elevating the AppBar:
Screen.Recording.2024-04-06.at.16.38.34.mov
The
MaterialApprebuilds once, andScaffoldmany times as theAppBarelevates and changes style via an animation. The elevation tint color is animated, as is the appearance of the shadow under it. This is all expected and as it should beNote
In the
AppBarThemetheshape: const RoundedRectangleBorder()is needed to make the change in elevation tint color animate as it should. That this is needed is due to a performance optimization that is made onMaterialwidget. See issue #131042 for more info.Trigger rebuild - Take 1
Next let us just push the button to trigger a rebuild, notifying listeners that the theme controller has changed and thus also the theme:
Screen.Recording.2024-04-06.at.16.59.52.mov
We get the same expected rebuild once of
MaterialApp, butScaffoldwas built many times, even if nothing in it changed. This is bad, but why did it happen?It happened because
ThemeDataobject value equality is broken, because we are using aMaterialStatePropertythat contain callback functions.In our
ThemeDatathestyleFromwill define a customMaterialStatePropertyfor ourAppBarThemeinThemeDataand this kills value equality ofThemeData.So the equality checks for rebuilding
ThemeDatano longer works correctly, due to the callback functions in it.Trigger rebuild - Take 2
To demo what happens when we do not have any custom
MaterialStatePropertydefined anywhere in ourThemeData, just remove or comment thetextButtonThemelines in the example theme:Again, let's start with a fresh build, we see that it has the 3 builds by the SDK as before, plus once for our theme change, but now also only once for the
Scaffold, no animation rebuild!! This is already better and what we would have expected for the start of the above examples too.Now again just trigger the rebuild:
Screen.Recording.2024-04-06.at.17.11.12.mov
And now we get the expected behavior, the
MaterialApprebuild is as ordered triggered once, but also theScaffoldwas only rebuilt once, as it should have been, since there was nothing changed in it that needed to be animated. The value of the newThemeDatawas identical to the previous one, so nothing to animate. However, as shown above earlier, as soon as we have and use any component theme that defines anyMaterialStatePorpertythe object equality is broken and we get all these extra rebuilds. Feel free to try with any otherMaterialStatePorpertyin any component sub theme inThemeData, and you will observe the same extra rebuild behavior.This is imo a bad when you are using custom component themes that contain
MaterialStatePorperty, and any serious custom theme will need a lot of them.This is caused by the current SDK design. I don't know of any workaround or fix to this rebuild issue.
HuynhThien82 commentedon May 8, 2024
In my case, I use GoRouter redirect, also I have a text field and one of my widget in the screen is using MediaQuery to get the screen size. Every time I touch the text field, my material app is rebuilt. It causes the flicker on the screen (because every time material app rebuild, it will call the go router navigation), the only way to fix it is removing my material app theme data (it also uses MediaQuery to check the platform).
disonwei commentedon May 12, 2024
When setting themeMode: ThemeMode.system, on iOS, entering and exiting the app background will also lead to rebuilding, causing screen flickering issues.
Console Output Results:
When the app enters the background, it will print once
When the app enters the foreground from the background, it will print once.
This is the question I asked before
#142890
justinmc commentedon Aug 27, 2024
I just discovered WidgetStateProperty.fromMap, which was recently added in #146043. Any idea if that works around this performance problem? I guess @nate-thegrate probably wasn't thinking of this performance problem when he wrote it, but even if it doesn't fix the problem out of the box, I think we could theoretically do it by adding a deep equality check to WidgetStateProperties created with fromMap.
nate-thegrate commentedon Aug 27, 2024
It's been very exciting to finally land the WidgetState mapping PR and talk about it here and in the recent design doc :)
Parsing the map in that constructor is something that hadn't even crossed my mind, but I bet it would prevent a lot of rebuilding (though the operators could make that process a bit more convoluted).
It would be a huge challenge, but maybe the most effective way to optimize rebuilds would be transitioning the
Themewidget to use anInheritedModel.MediaQueryuses a private enum type for its dependencies, butThemecould use an API that stores information for making high-precision queries (similar to what provider/riverpod/bloc already have), so we only trigger a rebuild when the query output changes.I expect over the coming months we can drastically reduce the number of rebuilds caused by
Theme.of(context)calls.Until then, if your theme data includes something like
TextButton.styleFrom()that isn't optimized for equality checks, it's probably best to assume that any time theMaterialAppwidget rebuilds, it's going to trigger anAnimatedThemetransition.An easy fix is to set a zero duration for
themeAnimationDuration(though obviously there wouldn't be a nice animation even when something does change).Perhaps something like
themeAnimationDuration: kDebugMode ? Duration.zero : Durations.short4would be ideal.un-break `ThemeData` equality (#154695)
flutter-triage-bot commentedon Jan 10, 2025
This issue is assigned to @nate-thegrate but has had no recent status updates. Please consider unassigning this issue if it is not going to be addressed in the near future. This allows people to have a clearer picture of what work is actually planned. Thanks!
nate-thegrate commentedon Jan 10, 2025
The original issue comment showed that
TextButton.styleFrom()was ruining the theme data equality check, and fortunately that bug is now fixed.For anyone experiencing a related problem: feel free to open another issue!
github-actions commentedon Jan 28, 2025
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.