Flutter使用Provider实现收藏列表增删的问题及解决
问题:Flutter Provider收藏功能重复添加实例
我用Flutter的Provider开发收藏旅程列表,现有代码能添加旅程,但再次点击收藏时following.list.contains(favorite)永远返回false,导致重复添加新的Favorite实例。原因是每次创建新对象,即使内容相同,对象引用不同,contains判断失效,求正确的增删实现方式。
相关代码与日志
Favorite.dart
import 'package:flutter/material.dart'; class Favorite extends ChangeNotifier { final Text date; final Text time; final Text source; final Text destination; final Text price; Favorite(this.date, this.time, this.source, this.destination, this.price); } class Following extends ChangeNotifier { List<Favorite> list = []; add(favorite) { list.add(favorite); notifyListeners(); } remove(favorite) { list.remove(favorite); notifyListeners(); } }
journeys.dart(旅程列表页面)
FirebaseAnimatedList( shrinkWrap: true, query: Consts.journeyRef.child("journeys"), itemBuilder: (BuildContext context, DataSnapshot snapshot, Animation animation, int index) { try { return Consumer<Following>( builder: (BuildContext context, value, child) { return Dismissible( key: UniqueKey(), secondaryBackground: buildSwipeActionRight(), background: buildSwipeActionLeft(), child: ListView( scrollDirection: Axis.vertical, shrinkWrap: true, children: <Widget>[ eachTile( following, Favorite( Text(Map<String, dynamic>.from( snapshot.value as Map)[Consts.pathDateJourney]), Text(Map<String, dynamic>.from( snapshot.value as Map)[Consts.pathTimeJourney]), Text(Map<String, dynamic>.from( snapshot.value as Map)[Consts.pathSourceCity]), Text(Map<String, dynamic>.from( snapshot.value as Map)[Consts.pathDestinationCity]), Text(Map<String, dynamic>.from( snapshot.value as Map)[Consts.pathPriceJourney]),),) ]), onDismissed: (direction) => dismissItem(context, index, direction), ); }, ); } catch (e) { customSnackBar(context, e.toString(), 3, Colors.white24, Colors.brown, 17); return const Text( "We Can't show you information disabled by the Administrator"); } }),
eachTile.dart
ListTile eachTile(following, favorite) { return ListTile( leading: Column( children: [ favorite.date, const SizedBox( height: 10, ), favorite.time, ], ), title: Row( children: [ favorite.source, const SizedBox( width: 50, ), favorite.price ], ), subtitle: favorite.destination, trailing: IconButton( icon: following.list.contains(favorite) ? const Icon(Icons.favorite) : const Icon(Icons.favorite_border_outlined), onPressed: () { print(following.list.contains(favorite)); // 该打印语句始终返回false if (following.list.contains(favorite)) { following.remove(favorite); } else { following.add(favorite); } print(following.list.contains(favorite)); // 该打印语句始终返回true print(following.list); // 该打印语句显示每次执行都会添加新实例 }, ), ); }
打印日志
I/flutter ( 578): false I/flutter ( 578): true I/flutter ( 578): [Instance of 'Favorite'] V/AutofillManager( 578): requestHideFillUi(null): anchor = null I/flutter ( 578): false I/flutter ( 578): true I/flutter ( 578): [Instance of 'Favorite', Instance of 'Favorite'] V/AutofillManager( 578): requestHideFillUi(null): anchor = null I/flutter ( 578): false I/flutter ( 578): true I/flutter ( 578): [Instance of 'Favorite', Instance of 'Favorite', Instance of 'Favorite']
解决方案
问题核心是对象引用不同导致contains判断失效:每次调用eachTile时都会创建新的Favorite实例,即使内容和已收藏的一致,它们的内存地址不同,contains(默认比较对象引用)会认为是不同对象。
步骤1:修改Favorite类,重写相等判断方法
让Favorite根据内容而非引用判断是否相等,同时把数据模型和UI组件分离:
import 'package:flutter/material.dart'; // 数据模型不需要继承ChangeNotifier,只负责存储数据 class Favorite { final String date; final String time; final String source; final String destination; final String price; Favorite(this.date, this.time, this.source, this.destination, this.price); // 重写==,根据内容判断是否相等 @override bool operator ==(Object other) => identical(this, other) || other is Favorite && runtimeType == other.runtimeType && date == other.date && time == other.time && source == other.source && destination == other.destination && price == other.price; // 配合==重写hashCode @override int get hashCode => date.hashCode ^ time.hashCode ^ source.hashCode ^ destination.hashCode ^ price.hashCode; } class Following extends ChangeNotifier { List<Favorite> list = []; // 自定义contains方法,根据内容判断是否存在 bool contains(Favorite favorite) { return list.any((item) => item == favorite); } void add(Favorite favorite) { if (!contains(favorite)) { list.add(favorite); notifyListeners(); } } void remove(Favorite favorite) { // 根据内容移除匹配的实例 list.removeWhere((item) => item == favorite); notifyListeners(); } }
步骤2:修改journeys.dart中创建Favorite的逻辑
从Firebase提取字符串数据,直接创建Favorite实例(不再用Text包裹):
FirebaseAnimatedList( shrinkWrap: true, query: Consts.journeyRef.child("journeys"), itemBuilder: (BuildContext context, DataSnapshot snapshot, Animation animation, int index) { try { final journeyData = Map<String, dynamic>.from(snapshot.value as Map); // 先提取字符串数据 final date = journeyData[Consts.pathDateJourney]; final time = journeyData[Consts.pathTimeJourney]; final source = journeyData[Consts.pathSourceCity]; final destination = journeyData[Consts.pathDestinationCity]; final price = journeyData[Consts.pathPriceJourney]; // 创建Favorite实例 final favorite = Favorite(date, time, source, destination, price); return Consumer<Following>( builder: (BuildContext context, following, child) { return Dismissible( key: UniqueKey(), secondaryBackground: buildSwipeActionRight(), background: buildSwipeActionLeft(), child: ListView( scrollDirection: Axis.vertical, shrinkWrap: true, children: <Widget>[ eachTile(following, favorite) ], ), onDismissed: (direction) => dismissItem(context, index, direction), ); }, ); } catch (e) { customSnackBar(context, e.toString(), 3, Colors.white24, Colors.brown, 17); return const Text("管理员已禁用该信息,无法显示"); } }),
步骤3:修改eachTile.dart,在UI层创建Text组件
把数据转换成UI组件的逻辑移到UI层:
ListTile eachTile(Following following, Favorite favorite) { return ListTile( leading: Column( children: [ Text(favorite.date), const SizedBox(height: 10), Text(favorite.time), ], ), title: Row( children: [ Text(favorite.source), const SizedBox(width: 50), Text(favorite.price) ], ), subtitle: Text(favorite.destination), trailing: IconButton( icon: following.contains(favorite) ? const Icon(Icons.favorite) : const Icon(Icons.favorite_border_outlined), onPressed: () { if (following.contains(favorite)) { following.remove(favorite); } else { following.add(favorite); } }, ), ); }
内容的提问来源于stack exchange,提问作者saad tabban
相关产品推荐
相关产品推荐

