I am having an issue with my flutter app – I have a screen with a ListView of widgets that each have a “like” button and when the like button is pressed the application reads a riverpod provider and updates the app state. The issue is that when this update happens the entire screen is reloading rather than just the widget that changed. I am using the same button widget in another screen and am not having this issue. Any help is greatly appreciated.
This is the code for the screen:
class FeedScreen extends ConsumerWidget {
const FeedScreen({super.key, required this.currentUserId});
final UserID currentUserId;
@override
Widget build(BuildContext context, WidgetRef ref) {
// final currentUserValue = ref.watch(getUserProvider(currentUserId));
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) {
// final userValue = ref.watch(getUserProvider(feedListVal[index].author));
// return AsyncValueWidget(
// value: userValue,
// data: (user) {
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")
)
],
)
],
),
)),
),
)
);
}
}
The like button widget code:
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")
);
}
}
}
The code for the like button controller:
@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;
}
}
}
And the code for the provider that pulls the screen’s content:
@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);
}
And the code for the controller: