Flutter底部导航栏开发遇StackOverflowError问题求助
问题重现
为Flutter项目添加BottomNavigationBar后,运行时触发StackOverflowError,报错指向Builder组件,完整报错信息如下:
Launching lib\main.dart on sdk gphone64 x86 64 in debug mode... lib\main.dart:1 √ Built build\app\outputs\flutter-apk\app-debug.apk. Connecting to VM Service at ws://127.0.0.1:62216/ZIcDnqzIkLk=/ws E/SurfaceSyncer( 7593): Failed to find sync for id=0 W/Parcel ( 7593): Expecting binder but got null! D/EGL_emulation( 7593): app_time_stats: avg=7.90ms min=2.99ms max=180.93ms count=62 D/EGL_emulation( 7593): app_time_stats: avg=15.06ms min=3.37ms max=21.43ms count=60 ════════ Exception caught by widgets library ═══════════════════════════════════ The following StackOverflowError was thrown building Builder: Stack Overflow The relevant error-causing widget was Builder lib\…\homepage\home_page.dart:31 When the exception was thrown, this was the stack
错误原因分析
从完整代码中定位到两个核心问题:
- 无限递归创建组件:全局
_widgetOptions列表第一个元素是const HomePage(),而HomePage的build方法又会加载_widgetOptions。默认选中索引_selectedIndex为0时,会无限递归创建HomePage实例,直接导致栈溢出。 - 页面结构逻辑错误:
Scaffold的body中同时放置了_widgetOptions.elementAt(_selectedIndex)和body(),重复渲染内容的同时加剧了递归问题。
修复方案
- 替换递归组件:将
_widgetOptions中的HomePage替换为首页内容组件(即原body()返回的Widget,单独抽成组件)。 - 调整页面结构:
Scaffold的body只显示当前选中的页面,移除重复的body()调用。 - 优化状态管理:将
_selectedIndex移到_HomePageState类内部,避免全局变量带来的状态混乱。
修复后完整代码
class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { int _selectedIndex = 0; final List<Widget> _widgetOptions = const <Widget>[ HomeContentPage(), // 替换原HomePage为首页内容组件 AcademicPage(), CreditPage(), ProfilePage(), ]; @override Widget build(BuildContext context) { return SafeArea( child: Scaffold( bottomNavigationBar: BottomNavigationBar( type: BottomNavigationBarType.fixed, selectedFontSize: 12, unselectedItemColor: Colors.grey, items: const <BottomNavigationBarItem>[ BottomNavigationBarItem( icon: Icon(Icons.home), label: 'Beranda', ), BottomNavigationBarItem( icon: Icon(Icons.school), label: 'Akademik', ), BottomNavigationBarItem( icon: Icon(Icons.credit_card), label: 'Pembayaran', ), BottomNavigationBarItem( icon: Icon(Icons.person), label: 'Profile', ), ], currentIndex: _selectedIndex, selectedItemColor: primaryColor, onTap: (index) { setState(() { _selectedIndex = index; }); }, ), body: _widgetOptions.elementAt(_selectedIndex), // 仅显示选中页面 ), ); } } // 抽离首页内容为单独组件 class HomeContentPage extends StatelessWidget { const HomeContentPage({super.key}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 30), Container( child: FutureBuilder<UserBiodata>( future: AuthProvider().getDataMahasiswa(), builder: (context, snapshot) { if (snapshot.hasData) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '${snapshot.data!.data.name}', style: bold4, ), Text( '${snapshot.data!.data.prodi}', style: bold6, ), Text( 'Semester ${snapshot.data!.data.smt}', style: regular6, ) ], ), Image.asset( 'assets/images/user.png', width: 60, ), ], ); } else if (snapshot.hasError) { return Text('${snapshot.error}'); } return const CircularProgressIndicator(); }, ), ), const SizedBox(height: 30), Image.asset('assets/images/statis.png'), const SizedBox(height: 30), Text('Akses Cepat', style: bold4), const SizedBox(height: 18), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildQuickAccessItem('Jadwal Kuliah', 'assets/images/icon_jadwal.png'), _buildQuickAccessItem('Jadwal Kuliah', 'assets/images/icon_jadwal.png'), _buildQuickAccessItem('Jadwal Kuliah', 'assets/images/icon_jadwal.png'), ], ), const SizedBox(height: 18), Text('Dosen', style: bold4), const SizedBox(height: 18), _buildLecturerCard(context), const SizedBox(height: 18), _buildLecturerCard(context), ], ), ); } Widget _buildQuickAccessItem(String label, String iconPath) { return Container( height: 90, width: 90, decoration: BoxDecoration( color: Colors.white, borderRadius: const BorderRadius.all(Radius.circular(20)), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.2), spreadRadius: 1, blurRadius: 9, offset: const Offset(1, 2), ), ], ), child: Column( children: [ const SizedBox(height: 10), Image.asset(iconPath, height: 40), const SizedBox(height: 12), Text(label, style: regular7), ], ), ); } Widget _buildLecturerCard(BuildContext context) { return Container( width: MediaQuery.of(context).size.width, decoration: BoxDecoration( color: Colors.white, borderRadius: const BorderRadius.all(Radius.circular(8)), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.2), spreadRadius: 1, blurRadius: 9, offset: const Offset(1, 2), ), ], ), child: Padding( padding: const EdgeInsets.all(14), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Muhamad Fahmi Nugraha M.Kom', style: bold6), Text('kaprodi Fakultas Komputer', style: bold7), const SizedBox(height: 12), Row( children: [ SizedBox( width: 94, height: 32, child: TextButton( onPressed: () => showDialog( context: context, builder: (BuildContext context) => _signOutDialog(context), ), style: TextButton.styleFrom( backgroundColor: primaryColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text("WhatsApp", style: boldButton1), ), ), const SizedBox(width: 8), SizedBox( width: 94, height: 32, child: TextButton( onPressed: () { Navigator.pushNamed(context, AppRoute.biodataeRoute); }, style: TextButton.styleFrom( backgroundColor: button, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text("Detail", style: boldButton2), ), ), ], ) ], ), Image.asset('assets/images/user.png', width: 50) ], ), ), ); } } Widget _signOutDialog(BuildContext context) { return AlertDialog( title: Column( children: [ Image.asset('assets/images/ic_lgt.png'), ], ), content: const Text('Apakah kamu yakin ingin logout?'), actions: <Widget>[ TextButton( onPressed: () => AuthProvider().signOut(context), child: const Text('Ya'), ), TextButton( onPressed: () => Navigator.pop(context), child: const Text( 'TIDAK', style: TextStyle(color: Colors.red), ), ), ], ); }
额外说明
- 抽离
HomeContentPage组件让代码结构更清晰,避免主页面堆积过多UI代码。 - 提取重复UI元素为单独方法,减少代码冗余。
- 移除了原代码中多余的
Builder组件和嵌套Padding,优化了布局层级。
内容的提问来源于stack exchange,提问作者sharon
相关产品推荐
相关产品推荐

