For unknown reasons, I found out that the CupertinoNavigationBar
causes huge lags and frame drops when transitioning between pages when the following conditions are met:
- its parent, the
CupertinoPageScaffold
, is a page displayed within aCupertinoTabScaffold
- the root navigator is used to push the page.
I was able to find that out when I commented out my CupertinoNavigationBar
because then the app became butter smooth again.
Also, I find no issue if I am not using the root navigator (meaning the new page is displayed within the active tab), but this is not what I want to do.
Finally, in order to test out my theory, if there is no CupertinoTabScaffold
, then I don't have the issue at all.
My issue only occurs on iPhone, even in profile mode.
The following is a minimum reproductible example:
import 'package:flutter/cupertino.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return const CupertinoApp(
title: 'Flutter Demo',
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
static const homePages = [
HistoryPage(),
MetricsPage(),
];
@override
Widget build(BuildContext context) {
return CupertinoTabScaffold(
tabBuilder: (_, index) => CupertinoTabView(
builder: (_) => homePages[index],
),
tabBar: CupertinoTabBar(
items: const [
BottomNavigationBarItem(
label: 'History',
icon: Icon(CupertinoIcons.square_list),
activeIcon: Icon(CupertinoIcons.square_list_fill),
),
BottomNavigationBarItem(
label: 'Metrics',
icon: Icon(CupertinoIcons.chart_bar_square),
activeIcon: Icon(CupertinoIcons.chart_bar_square_fill),
),
],
),
resizeToAvoidBottomInset: false,
);
}
}
class HistoryPage extends StatelessWidget {
const HistoryPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: const Text('History Tab'),
trailing: CupertinoButton(
padding: EdgeInsets.zero,
child: const Icon(CupertinoIcons.add),
onPressed: () {
Navigator.of(context, rootNavigator: true)
.push(CupertinoPageRoute(builder: (context) => const SimplePage()));
},
),
),
child: const Center(
child: Text('This is the History Tab'),
),
);
}
}
class MetricsPage extends StatelessWidget {
const MetricsPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Metrics Tab'),
),
child: Center(
child: Text('This is the Metrics Tab'),
),
);
}
}
class SimplePage extends StatelessWidget {
const SimplePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: const Text('Simple Page'),
trailing: CupertinoButton(
child: const Icon(CupertinoIcons.add),
onPressed: () {
Navigator.of(context, rootNavigator: true)
.push(CupertinoPageRoute(builder: (context) => const SimplePage()));
},
),
),
child: const Center(
child: Text('This is a simple page'),
),
);
}
}
Try to comment out the navigation bar and place the button in the child instead of the text and notice how differently it performs.
I encountered the same problem with this widget configuration with Flutter 3.16.8. Disabling the impeller rendering backend fixed the problem.
See:
https://docs.flutter.dev/perf/impeller