我的 flutter 应用程序遇到问题 - 我有一个带有小部件 ListView 的屏幕,每个小部件都有一个“喜欢”按钮,当按下“喜欢”按钮时,应用程序会读取 Riverpod 提供程序并更新应用程序状态。问题是,当此更新发生时,整个屏幕都会重新加载,而不仅仅是发生更改的小部件。我在另一个屏幕上使用相同的按钮小部件,但没有遇到此问题。非常感谢任何帮助。
这是屏幕代码:
class FeedScreen extends ConsumerWidget {
const FeedScreen({super.key, required this.currentUserId});
final UserID currentUserId;
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(),
body: ResponsiveCenter(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
Expanded(child: FeedList(currentUserUid: currentUserId,)),
]))
);
}
}
class FeedList extends ConsumerWidget {
const FeedList({super.key, required this.currentUserUid});
final String currentUserUid;
@override
Widget build(BuildContext context, WidgetRef ref) {
debugPrint("FEED LIST");
final feedListVal = ref.watch(fetchFeedProvider);
// ScrollController controller = ScrollController();
return AsyncValueWidget(
value: feedListVal,
data: (feedListVal) {
return feedListVal.isEmpty
? const Center(child: Text("Feed is empty"))
: ListView.builder(
itemCount: feedListVal.length,
itemBuilder: (BuildContext context, int index) {
return FeedItem(timelineItem: feedListVal[index], userId: currentUserUid);
});
}
);
}
}
class FeedItem extends StatelessWidget {
const FeedItem({
super.key,
required this.timelineItem,
required this.userId
});
final TimelineItem timelineItem;
final String userId;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
context.goNamed(
AppRoute.eventDetails.name,
pathParameters: {
'eventId': timelineItem.eventId
}
);
},
child: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Card(child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Row(
children: [
CircleAvatar(backgroundImage: NetworkImage(timelineItem.authorPfpUrl),),
Padding(
padding: const EdgeInsets.all(4.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
timelineItem.event != null
? '${timelineItem.authorName} added content to ${timelineItem.event!.name}'
: timelineItem.authorName,
textAlign: TextAlign.start,),
Text(Jiffy.parseFromDateTime(timelineItem.createdAt).fromNow(), style: const TextStyle(color: Colors.grey),)
],
),
),
],
),
const Divider(),
Text(timelineItem.text),
if (timelineItem.imageUrls.isNotEmpty)
// TODO: make this show more than one image or expand to view all images
Image.network(timelineItem.imageUrls[0]),
const Divider(),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("${timelineItem.likes.length} Likes", textAlign: TextAlign.left,),
Text("${timelineItem.comments.length} Comments", textAlign: TextAlign.right,)
],
),
const Divider(),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
LikeTimelineItemButtonWidget(
eventId: timelineItem.eventId,
timelineItemId: timelineItem.id,
currentUserUid: userId,
likedByIds: timelineItem.likes
),
ElevatedButton.icon(
onPressed: () {
// _showFullModal(context, timelineItem.id);
},
icon: const Icon(Icons.comment),
label: const Text("Comment")
)
],
)
],
),
)),
),
)
);
}
}
点赞按钮小部件代码:
class LikeTimelineItemButtonWidget extends ConsumerWidget {
const LikeTimelineItemButtonWidget({
// required this.uid,
required this.eventId,
required this.timelineItemId,
required this.currentUserUid,
required this.likedByIds,
super.key,
});
// final UserID uid;
final EventID eventId;
final String timelineItemId;
final UserID currentUserUid;
final List<UserID> likedByIds;
@override
Widget build(BuildContext context, WidgetRef ref) {
ref.listen<AsyncValue>(
likeTimelineItemControllerProvider,
(_, state) => state.showAlertDialogOnError(context)
);
final state = ref.watch(likeTimelineItemControllerProvider);
final liked = likedByIds.contains(currentUserUid);
if (liked) {
return ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor.withOpacity(0.4),
),
onPressed: () => {
debugPrint("like button pressed"),
ref
.read(likeTimelineItemControllerProvider.notifier)
.likeTimelineItem(eventId, timelineItemId, currentUserUid, likedByIds)
},
icon: Icon(Icons.thumb_up),
label: Text("Like")
);
} else {
return ElevatedButton.icon(
onPressed: () => {
debugPrint("like button pressed"),
ref
.read(likeTimelineItemControllerProvider.notifier)
.likeTimelineItem(eventId, timelineItemId, currentUserUid, likedByIds)
},
icon: Icon(Icons.thumb_up),
label: Text("Like")
);
}
}
}
点赞按钮控制器的代码:
@riverpod
class LikeTimelineItemController extends _$LikeTimelineItemController with NotifierMounted {
@override
FutureOr<void> build() {
ref.onDispose(setUnmounted);
}
Future<bool> likeTimelineItem(EventID eventId, String timelineItemId, UserID likingUserID, List<UserID> likedByIds) async {
try {
final timelineItemsRepository = ref.read(timelineItemsRepositoryProvider);
state = const AsyncLoading();
final value = await AsyncValue.guard(
() => timelineItemsRepository.toggleLikeTimelineItem(timelineItemId, likingUserID, eventId, likedByIds));
final success = value.hasError == false;
if (mounted) {
state = value;
}
return success;
} catch (e, st) {
debugPrint(e.toString());
debugPrint(st.toString());
if (mounted) {
state = AsyncError(e, st);
}
return false;
}
}
}
以及提取屏幕内容的提供者的代码:
@riverpod
Future<List<TimelineItem>> fetchFeed(FetchFeedRef ref) async {
final user = await ref.watch(watchUserProvider.future);
debugPrint("[FEED SERVICE - fetchFeed] - user: $user");
final friends = await ref.watch(friendsListStreamProvider(user).future);
debugPrint("[FEED SERVICE - fetchFeed] - friends: $friends");
return friends.isEmpty
? []
: await ref.watch(
watchFeedProvider(friends.map((user) => user.id).toList()).future);
}
控制器的代码:
Future<bool> likeTimelineItem(EventID eventId, String timelineItemId, UserID likingUserID, List<UserID> likedByIds) async {
try {
final timelineItemsRepository = ref.read(timelineItemsRepositoryProvider);
state = const AsyncLoading();
final value = await AsyncValue.guard(
() => timelineItemsRepository.toggleLikeTimelineItem(timelineItemId, likingUserID, eventId, likedByIds));
final success = value.hasError == false;
if (mounted) {
state = value;
}
return success;
} catch (e, st) {
debugPrint(e.toString());
debugPrint(st.toString());
if (mounted) {
state = AsyncError(e, st);
}
return false;
}
}
这已经是很多代码了,但我认为还需要更多代码才能明确地告诉问题出在哪里。但是,您提供的代码中有一个可以开始的地方。
在您的 FeedList 小部件中,您有以下行:
final feedListVal = ref.watch(fetchFeedProvider);
这意味着 FeedList 小部件将在每次 fetchFeedProvider 触发时重建。这是需要更多信息来确定的地方,但一个有效的假设是,只要它提供的数据发生任何变化(其中包括
friendsListStreamProvider(user)
状态),就会重新触发 like
。
如果是这种情况,那么你必须弄清楚你要做什么。这将取决于您的数据库以及您如何实施您的提供商。
实际上,只需在列表项上添加
ValueKey
以及 ID 和任何可能更改的属性就足够了,因为这将阻止列表项在不需要时重新呈现。
但是,如果您根本不想调用该构建函数,解决方案将如下所示:
ValueKey
添加到以项目 ID 为键的 FeedItem 对象中,并且仅传入 ID(这应该确保即使再次调用构建函数,也不会触发重新布局或重新布局)绘制整个列表FeedItem
对象中使用提供程序来获取每个项目的数据。请注意,如果它为列表中的每个项目执行单独的数据库调用,则效率可能会很低,但如果您已使用缓存将数据库调用和提供程序解耦,则可以使用它,以便 fetchFeedProvider 仍然执行数据库调用,并且缓存保存随后将被请求的所有项目。如果您执行了所有这些操作,那么不仅在项目更改时整个列表不会重建,而且如果确实如此,列表也不应该重新渲染。需要注意的一件事是,您可能无法仅在提供程序中使用列表 - 默认情况下,它们不会实现任何类型的相等,因此两个不同的列表即使具有相同的元素也永远不会相等。您可能需要将其包装在提供适当相等性的内容中,或者使用
built_collection
包中的内置列表之类的内容。