如何无需热重载更新Firebase Firestore驱动的Widget可见性?
问题描述
使用StreamBuilder从Firebase Firestore获取任务数据,展示两个列表:一个是所有未完成任务,另一个是taskStatus为true的已完成任务。当前功能正常,但需要实现已完成任务列表为空时自动隐藏整个Widget区域,且无需热重载就能实时更新可见性。之前尝试用Opacity调整透明度,但必须热重载才生效,需要优化实现方式。
解决方案
核心思路是直接在StreamBuilder的builder回调中根据数据状态返回不同组件:
- 当已完成任务列表为空时,返回
SizedBox.shrink()(完全不占用布局空间,相当于隐藏) - 当列表有数据时,返回完整的已完成任务列表组件
这种方式依赖StreamBuilder的数据流监听特性,Firestore数据变化时会自动触发builder重建,无需热重载即可实时更新可见性。
修改后的完整代码
body: Column( children: [ // 未完成任务列表 Expanded( child: StreamBuilder<List<Task>>( stream: DatabaseService().getTasks(orderName), builder: (context, snapshot) { if (!snapshot.hasData) { return Loading(); } List<Task>? tasks = snapshot.data; if (snapshot.data!.isEmpty) { return StreamBuilder( stream: DatabaseService().getUniqueIDs(), builder: (context, snapshot) => Column( children: [ Container( alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 30, vertical: 40), child: Text( "Tasks", style: TextStyle( fontSize: 30, fontWeight: FontWeight.bold, color: Colors.grey[700], ), ), ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 30), child: Container( alignment: Alignment.center, child: const Text( "Great! You don't have any remaining tasks.", style: TextStyle( fontSize: 20, fontFamily: "Roboto", color: Colors.black, ), ), ), ), const SizedBox( height: 40, ), Image( image: const AssetImage('assets/images/tasks.png'), height: MediaQuery .of(context) .size .height * 0.3, ), ], ), ); } return Column( children: [ Container( alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 30, vertical: 40), child: Text( "Tasks", style: TextStyle( fontSize: 30, fontWeight: FontWeight.bold, color: Colors.grey[700], ), ), ), ), Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 15), child: ListView.separated( separatorBuilder: (context, index) => const SizedBox( height: 5, ), itemCount: tasks!.length, itemBuilder: (context, index) { return Dismissible( background: Container( color: Colors.red, child: const Align( alignment: Alignment.centerLeft, child: Padding( padding: EdgeInsets.all(15.0), child: Icon( Icons.delete, color: Colors.white, ), ), ), ), secondaryBackground: Container( color: Colors.red, child: const Align( alignment: Alignment.centerRight, child: Padding( padding: EdgeInsets.all(15.0), child: Icon( Icons.delete, color: Colors.white, ), ), ), ), key: UniqueKey(), onDismissed: (direction) { DatabaseService() .deleteTask(tasks[index].id); AwesomeNotifications() .cancelSchedule(tasks[index].id); }, child: Item( task: tasks[index], ), ); }, ), ), ), ], ); }), ), // 已完成任务列表(优化后) StreamBuilder<List<Task>>( stream: DatabaseService().getCompletedTasks(orderName), builder: (context, snapshot) { if (!snapshot.hasData) { return Loading(); } // 数据为空时返回空组件,不占用布局空间 if (snapshot.data!.isEmpty) { return SizedBox.shrink(); } List<Task>? completedTasks = snapshot.data; // 有数据时返回完整的列表区域 return Expanded( child: Column( children: [ Container( alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 30, vertical: 20), child: Text( "Completed Tasks", style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.grey[700], ), ), ), ), Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 15), child: ListView.separated( separatorBuilder: (context, index) => const SizedBox( height: 5, ), itemCount: completedTasks!.length, itemBuilder: (context, index) { return Dismissible( background: Container( color: Colors.red, child: const Align( alignment: Alignment.centerLeft, child: Padding( padding: EdgeInsets.all(15.0), child: Icon( Icons.delete, color: Colors.white, ), ), ), ), secondaryBackground: Container( color: Colors.red, child: const Align( alignment: Alignment.centerRight, child: Padding( padding: EdgeInsets.all(15.0), child: Icon( Icons.delete, color: Colors.white, ), ), ), ), key: UniqueKey(), onDismissed: (direction) { DatabaseService() .deleteTask(completedTasks[index].id); AwesomeNotifications().cancelSchedule( completedTasks[index].id); }, child: Item( task: completedTasks[index], ), ); }, ), ), ), ], ), ); }), ], ),
关键改动说明
- 调整了已完成任务列表的结构:将
Expanded从外部移到StreamBuilder内部,仅在有数据时返回带Expanded的组件,空数据时返回SizedBox.shrink()完全不占空间 - 移除了
Opacity组件,改为直接返回不同Widget,避免了透明但仍占用布局的问题 - 利用StreamBuilder的自动监听特性,Firestore数据更新时会立即重建组件,实现实时的显示/隐藏切换,无需热重载
内容的提问来源于stack exchange,提问作者Faseeh Hyder
相关产品推荐
相关产品推荐

