调用setState时FutureBuilder反复重建,星星状态无法更新
解决Flutter Dashboard中星星按钮状态无法即时更新的问题
问题根源
- 使用单个全局
isGolden变量存储所有GridView项的收藏状态,每个项在itemBuilder中都会覆盖该变量的值 - 调用
setState后FutureBuilder重建,itemBuilder会重新从API返回的旧数据中赋值isGolden,导致手动切换的状态被覆盖,无法即时显示
解决方案
改用**Map<String, bool>**(以故事ID为键,更稳定)单独存储每个项的收藏状态,避免全局变量被覆盖;同时处理API请求的成功/失败,失败时回滚本地状态,保证数据一致性。
修改后的关键代码部分
- 替换状态变量
// 替换原来的单个bool isGolden Map<String, bool> favoriteStates = {}; // 用故事ID作为键,确保状态与对应项绑定
- 初始化状态Map
在FutureBuilder获取到数据后,从API返回的内容中填充状态Map:
if(snapshot.hasData){ // 初始化每个故事的收藏状态 favoriteStates = { for(var story in snapshot.data!.data!) story.id.toString(): story.favoriteStories!.length > 0 }; // 后续GridView代码... }
- 在itemBuilder中获取对应项的状态
itemBuilder: (BuildContext context, index){ final story = snapshot.data!.data![index]; final storyId = story.id.toString(); // 从Map中获取当前项的状态,不再覆盖全局变量 final isGolden = favoriteStates[storyId] ?? false; // 后续Card代码... }
- 修改星星按钮的点击逻辑
先切换本地状态实现即时反馈,再处理API请求,失败时回滚状态:
onPressed: (){ final storyId = snapshot.data!.data![index].id.toString(); // 先切换本地状态,即时更新UI setState(() { favoriteStates[storyId] = !(favoriteStates[storyId] ?? false); }); // 发起API请求 futureMessage = PostStoryFavoriteCreateDelete(storyId); ScaffoldMessenger.of(context).showSnackBar(SnackBar( elevation: 20, content: FutureBuilder( future: futureMessage, builder: (context, snackSnapshot){ if (snackSnapshot.hasData) { return const Text("SuccessFully Updated"); } else if (snackSnapshot.hasError) { // API请求失败,回滚本地状态 setState(() { favoriteStates[storyId] = !(favoriteStates[storyId] ?? false); }); return const Text("Failed To Update"); } return const Text("Processing..."); }, ), )); }
完整修改后的_DashBoardState代码
class _DashBoardState extends State<DashBoard> { final drawerWidget = const DrawerWidget(); late Future<StoriesMobilePrivate> futureStoriesMobilePrivate; late Future<StoryFavoriteCreateDelete> futureMessage; // 替换单个isGolden为存储每个故事状态的Map Map<String, bool> favoriteStates = {}; @override void initState() { super.initState(); futureStoriesMobilePrivate = fetchFavoriteStoriesPrivate(); } @override Widget build(BuildContext context) { return Scaffold( resizeToAvoidBottomInset: true, extendBodyBehindAppBar: true, appBar: AppBar( backgroundColor: Colors.transparent, centerTitle: true, elevation: 0.0, title: const Text( "My Talez", style: TextStyle( color: Colors.black87, fontWeight: FontWeight.bold, fontSize: 23), ), iconTheme: const IconThemeData(color: Colors.black87), actions: [ IconButton(onPressed: (){}, icon: Image.asset("assets/images/Frame.png")) ], ), drawer: drawerWidget, body: Container( width: MediaQuery.of(context).size.width, decoration: const BoxDecoration( image: DecorationImage( image: AssetImage("assets/images/dashboard.png"), fit: BoxFit.cover, ) ), child: Padding( padding: const EdgeInsets.only(top: 100), child: Column( children: [ FutureBuilder( future: futureStoriesMobilePrivate, builder: (context, snapshot) { if(snapshot.hasData){ // 初始化收藏状态Map favoriteStates = { for(var story in snapshot.data!.data!) story.id.toString(): story.favoriteStories!.length > 0 }; return SingleChildScrollView( child: SizedBox( height: MediaQuery.of(context).size.height*.8, child: GridView.builder( padding: const EdgeInsets.all(20), itemCount: snapshot.data!.data!.length, gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( childAspectRatio: .85, crossAxisCount: 2, crossAxisSpacing: 8, mainAxisSpacing: 8, ), itemBuilder: (BuildContext context, index){ final story = snapshot.data!.data![index]; final storyId = story.id.toString(); final isGolden = favoriteStates[storyId] ?? false; return Card( elevation: 7, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), color: const Color(0xFF609FFB), child: Container( child: Column( children: [ Stack( children: [ story.userStoryPages![0].mergedImage == null? Container( height: 160, width: 160, margin: const EdgeInsets.only(top: 8, right: 8, left: 8, bottom: 1), decoration: const BoxDecoration( color: Colors.white, image: DecorationImage( fit: BoxFit.fitHeight, image: AssetImage("assets/images/image 14.png"), ) ), ):Container( height: 160, width: 160, margin: const EdgeInsets.only(top: 8, right: 8, left: 8, bottom: 1), decoration: BoxDecoration( image: DecorationImage( fit: BoxFit.cover, image: NetworkImage(story.userStoryPages![0].mergedImage.toString()) ) ), ), Positioned( top: 12, right: 12, child: SizedBox( height: 25, width: 25, child: CircleAvatar( backgroundColor: isGolden? Colors.amber :Colors.black87, child: IconButton( padding: EdgeInsets.zero, icon: const Icon(Icons.star), color: Color(0xFFFFFFD5), onPressed: (){ // 先切换本地状态,即时更新UI setState(() { favoriteStates[storyId] = !(favoriteStates[storyId] ?? false); }); // 发起API请求 futureMessage = PostStoryFavoriteCreateDelete(storyId); ScaffoldMessenger.of(context).showSnackBar(SnackBar( elevation: 20, content: FutureBuilder( future: futureMessage, builder: (context, snackSnapshot){ if (snackSnapshot.hasData) { return const Text("SuccessFully Updated"); } else if (snackSnapshot.hasError) { // API失败,回滚状态 setState(() { favoriteStates[storyId] = !(favoriteStates[storyId] ?? false); }); return const Text("Failed To Update"); } return const Text("Processing..."); }, ), )); }, ), ), ), ) ], ), Padding( padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Text(story.title.toString(), style: const TextStyle(color: Colors.white), ), ], ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Icon(Icons.share, size: 18, color: Colors.white,), const SizedBox(width: 5,), Image.asset("assets/images/delete 6.png", height: 22, ) ], ), ], ), ), ], ), ), ); }, ), ), ); } else if (snapshot.hasError){ return Text(snapshot.error.toString()); } return const CircularProgressIndicator(); } ), Card( color: const Color(0xFFF96162), elevation: 8, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(4), ), child: SizedBox( height: 55, width: MediaQuery.of(context).size.width*.84, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset("assets/images/drawer icons/script 1.png"), const SizedBox(width: 7,), const Text("Create My Talez", style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), ), ], ) ), ) ], ), ), ), ); } }
内容的提问来源于stack exchange,提问作者Rehan
相关产品推荐
相关产品推荐

