FadeForwardsPageTransitionsBuilder flickers for AppBars with dark background
Steps to reproduce
Using FadeForwardsPageTransitionsBuilder (e. g. in Android or by setting it by hand like in the example).
- Create two screens each containing a Scaffold with an AppBar with a dark background.
- Navigate from the first screen to the second and back.
Expected results
The AppBar should keep its dark color while navigating.
Actual results
The AppBar flickers to a bright color.
Code sample
Code sample
import 'package:flutter/material.dart';
/// Based on https://api.flutter.dev/flutter/material/FadeForwardsPageTransitionsBuilder-class.html
/// changed AppBar backgroundColor to black and removed unnecessary parts
void main() => runApp(const PageTransitionsThemeApp());
class PageTransitionsThemeApp extends StatelessWidget {
const PageTransitionsThemeApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
pageTransitionsTheme: PageTransitionsTheme(
builders: Map<TargetPlatform, PageTransitionsBuilder>.fromIterable(
TargetPlatform.values,
value: (_) => const FadeForwardsPageTransitionsBuilder(),
),
),
),
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
foregroundColor: Colors.white,
backgroundColor: Colors.black,
),
body: Center(
child: FilledButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => SecondPage()));
},
child: Text("Navigate"),
),
),
);
}
}
class SecondPage extends StatelessWidget {
const SecondPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
foregroundColor: Colors.white,
backgroundColor: Colors.black,
),
body: Center(child: Text("Second Screen")),
);
}
}
Screenshots or Video
Video demonstration
https://github.com/user-attachments/assets/f72b8fa9-d9cc-4612-b135-dc926ba894f7
Logs
No response
Flutter Doctor output
Also reproducible on DartPad
Doctor output
[✓] Flutter (Channel stable, 3.38.4, on macOS 26.1 25B78 darwin-arm64, locale en-DE) [463ms]
• Flutter version 3.38.4 on channel stable at /Users/olischne/fvm/versions/stable
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 66dd93f9a2 (5 days ago), 2025-12-03 14:56:10 -0800
• Engine revision a5cb96369e
• Dart version 3.10.3
• DevTools version 2.51.1
• Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations, enable-native-assets, omit-legacy-version-file,
enable-lldb-debugging
[✓] Android toolchain - develop for Android devices (Android SDK version 36.1.0) [1,663ms]
• Android SDK at /Users/olischne/Library/Android/sdk
• Emulator version 36.3.10.0 (build_id 14472402) (CL:N/A)
• Platform android-36, build-tools 36.1.0
• Java binary at: /Users/olischne/Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
This is the JDK bundled with the latest Android Studio installation on this machine.
To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.
• Java version OpenJDK Runtime Environment (build 21.0.8+-14196175-b1038.72)
• All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 26.0.1) [1,391ms]
• Xcode at /Applications/Xcode.app/Contents/Developer
• Build 17A400
• CocoaPods version 1.16.2
[✓] Chrome - develop for the web [6ms]
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[✓] Connected device (4 available) [9.3s]
• sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64 • Android 16 (API 36) (emulator)
• iPhone 17 Pro (mobile) • 87B49BC9-D1F0-4E16-8744-53847B35AA7D • ios • com.apple.CoreSimulator.SimRuntime.iOS-26-0 (simulator)
• macOS (desktop) • macos • darwin-arm64 • macOS 26.1 25B78 darwin-arm64
• Chrome (web) • chrome • web-javascript • Google Chrome 143.0.7499.40
[✓] Network resources [1,751ms]
• All expected network resources are available.
• No issues found!
cc @MitchellGoodwin @QuncCccccc
Thanks for the report. Seeing the same behavior on latest stable and master versions.
stable: 3.38.4
master: 3.40.0-1.0.pre-55
This happens because a colored box is put behind the pages as they are sliding in. The color matches the surface color of the theme by default, but you can set it manually. In this case because the header is practically the opposite color from the background color of the content, it triggers this flash. You can get somewhat of a workaround by setting the background color of the transition to transparent, which keeps the flash from happening, but it causes the black from behind the app itself to show through a little during the transition, which is not preferable.
builders: Map<TargetPlatform, PageTransitionsBuilder>.fromIterable(
TargetPlatform.values,
value: (_) => const FadeForwardsPageTransitionsBuilder(backgroundColor: Colors.transparent),
),
https://github.com/user-attachments/assets/7f757cd5-ae78-486d-9b4d-8a9fcb5a1344
Same thing with ZoomPageTransitionsBuilder:
// A transition builder that takes into account the snapshotting properties of
// ZoomPageTransitionsBuilder.
static Widget _snapshotAwareDelegatedTransition(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget? child,
bool allowSnapshotting,
bool allowEnterRouteSnapshotting,
Color? backgroundColor,
) {
final Color enterTransitionBackgroundColor =
backgroundColor ?? Theme.of(context).colorScheme.surface; /// << HERE
return DualTransitionBuilder(
animation: ReverseAnimation(secondaryAnimation),
forwardBuilder: (BuildContext context, Animation<double> animation, Widget? child) {
return _ZoomEnterTransition(
animation: animation,
allowSnapshotting: allowSnapshotting && allowEnterRouteSnapshotting,
reverse: true,
backgroundColor: enterTransitionBackgroundColor,
child: child,
);
},
reverseBuilder: (BuildContext context, Animation<double> animation, Widget? child) {
return _ZoomExitTransition(
animation: animation,
allowSnapshotting: allowSnapshotting,
child: child,
);
},
child: child,
);
}
Fortunately, setting backgroundColor: Colors.transparent fixes the problem. But when using PredictiveBackFullscreenPageTransitionsBuilder, it can fall back to the const ZoomPageTransitionsBuilder(), where we can't set backgroundColor. The same thing happens when PredictiveBackPageTransitionsBuilder falls back to const FadeForwardsPageTransitionsBuilder().