-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Description
Steps to reproduce
- Install Flutter 3.27 with this commit applied (just a one line change): 4503f2f
- Run the attached sample app on MacOS or iOS.
- Click "Go to Second Page" button
- Click the back button at top left, and don't move the cursor
- App will assert as soon as the page transition completes if cursor is in position to invoke the PopupMenu's tooltip
Note 1: If you don't apply the aforementioned commit, you will instead crash when attempting to use the PopupMenu after navigation. That issue was fixed by the commit and that bug has been closed.
Note 2: If you set the tooltip for the PopupMenu to empty string, the assert does not occur (see commented out code in example)
Expected results
App doesn't crash/assert
Actual results
App asserts when rendering the tooltip for the PopupMenu. Top of call stack shown here:
_AssertionError._doThrowNew (flutter/bin/cache/pkg/sky_engine/lib/_internal/vm/lib/errors_patch.dart:63)
_AssertionError._throwNew (flutter/bin/cache/pkg/sky_engine/lib/_internal/vm/lib/errors_patch.dart:45)
RenderBox.size (flutter/packages/flutter/lib/src/rendering/box.dart:2251)
RenderFractionalTranslation.applyPaintTransform (flutter/packages/flutter/lib/src/rendering/proxy_box.dart:2974)
RenderObject.getTransformTo (flutter/packages/flutter/lib/src/rendering/object.dart:3520)
RenderBox.localToGlobal (flutter/packages/flutter/lib/src/rendering/box.dart:3082)
TooltipState._buildTooltipOverlay (flutter/packages/flutter/lib/src/material/tooltip.dart:793)
Builder.build (flutter/packages/flutter/lib/src/widgets/basic.dart:7818)
Code sample
Code sample
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
home: const CupertinoHomeScaffold(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
void _handleMenuSelection(String value) {
debugPrint('Menu selection: $value');
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text('Home Page'),
leading: PopupMenuButton<String>(
icon: const Icon(Icons.more_vert),
//tooltip: '', // empty tooltip prevents the crash
onSelected: _handleMenuSelection,
itemBuilder:
(BuildContext context) => [
const PopupMenuItem<String>(
value: 'settings',
child: Text('Settings'),
),
const PopupMenuItem<String>(
value: 'about',
child: Text('About'),
),
],
),
),
body: Center(
child: TextButton(
onPressed:
() => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SimplePage(title: 'Second Page'),
),
),
child: const Text('Go to Second Page'),
),
),
);
}
}
class SimplePage extends StatelessWidget {
const SimplePage({super.key, required this.title});
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: const Center(child: Text('Nothing to see here.')),
);
}
}
class CupertinoHomeScaffold extends StatefulWidget {
const CupertinoHomeScaffold({super.key});
@override
CupertinoHomeScaffoldState createState() => CupertinoHomeScaffoldState();
}
class CupertinoHomeScaffoldState extends State<CupertinoHomeScaffold> {
CupertinoTabController controller = CupertinoTabController();
@override
Widget build(BuildContext context) {
return CupertinoTabScaffold(
controller: controller,
tabBar: CupertinoTabBar(
key: const Key(Keys.tabBar),
items: TabItem.values.map((ti) => _buildItem(ti)).toList(),
onTap:
(index) => setState(() {
controller.index = index;
}),
),
tabBuilder: (context, index) {
final tabItem = TabItem.values[index];
return CupertinoTabView(
navigatorKey: navigatorKeys[tabItem],
builder:
(context) =>
tabItem == TabItem.incidents
? const MyHomePage()
: const SimplePage(title: 'Responders'),
);
},
);
}
BottomNavigationBarItem _buildItem(TabItem tabItem) {
final itemData = TabItemData.allTabs[tabItem]!;
final color =
controller.index == tabItem.index ? Colors.indigo : Colors.grey;
return BottomNavigationBarItem(
icon: Icon(itemData.icon, color: color),
label: itemData.title,
);
}
}
enum TabItem { incidents, responders }
class Keys {
static const String tabBar = 'tabBar';
static const String incidentsTab = 'incidentsTab';
static const String respondersTab = 'respondersTab';
}
final Map<TabItem, GlobalKey<NavigatorState>> navigatorKeys = {
TabItem.incidents: GlobalKey<NavigatorState>(),
TabItem.responders: GlobalKey<NavigatorState>(),
};
class TabItemData {
const TabItemData({
required this.key,
required this.title,
required this.icon,
});
final String key;
final String title;
final IconData icon;
static const Map<TabItem, TabItemData> allTabs = {
TabItem.incidents: TabItemData(
key: Keys.incidentsTab,
title: 'Incidents',
icon: Icons.work,
),
TabItem.responders: TabItemData(
key: Keys.respondersTab,
title: 'Responders',
icon: Icons.person,
),
};
}
Screenshots or Video
Screenshots / Video demonstration
[Upload media here]
Logs
Logs
[Paste your logs here]Flutter Doctor output
Doctor output
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.29.2, on macOS 15.3.2 24D81 darwin-arm64, locale en-US)
[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.1)
[✓] Xcode - develop for iOS and macOS (Xcode 16.3)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2024.3)
[✓] Connected device (4 available)
[✓] Network resources
• No issues found!