Reputation: 91
I am using animated list to show a real time notification history on my app. I use subscriptions to open a web socket and get updates realtime.
This is the widget that contains the list
void initState() {
super.initState();
_notifications = initialNotificationList; // I get this from the parent
WidgetsBinding.instance!.addPostFrameCallback((_) => _subscribe()); // I open the web socket after rendering the widget
}
// This opens the subscription
Future<bool> _subscribe() async {
try {
_subscription = await ApiService().subscriptions.subscribeNotifications(_onUpdate, _onError);
return true;
} catch (err) {
ToastrService().warning(context, "notificationsSubscriptionError".tr());
return false;
}
}
// Callback when new data arrives
void _onUpdate(List<Notifications> data) {
Notifications newMessage = data.first; // Notifications are ordered in desc chronological order
if ( (_notifications.singleWhereOrNull((el) => el.id == newMessage.id)) == null ) {
setState(() {
_notifications.insert(0, newMessage); // I insert the new notification as first element of the list (my model)
listKey.currentState!.insertItem(0, duration: const Duration(milliseconds: 500)); // I call insert item
});
}
// Animated List
child: AnimatedList(
key: listKey,
initialItemCount: widget.items.length,
scrollDirection: Axis.vertical,
itemBuilder: (context, index, animation) {
// I debugged here and the data that I am passing to the children is correct
return SizeIt(
animation: animation,
widget: NotificationCard(
message: _notifications[index].message,
createdAt: _notifications[index].createdAt,
isRead: _notifications[index].read,
id: _notifications[index].id,
),
);
},
)
This is my NotificationCard class:
class NotificationCard extends StatefulWidget {
final String id;
final String message;
final String createdAt;
final bool isRead;
const NotificationCard({
Key? key,
required this.id,
required this.message,
required this.createdAt,
required this.isRead,
}) : super(key: key);
@override
State<NotificationCard> createState() => _NotificationCardState();
}
class _NotificationCardState extends State<NotificationCard> {
late bool _isRead;
@override
void initState() {
// THIS IS NOT CALLED AFTER insertItem!! I need to hot refresh the app to make it work
// it only calls build
_isRead = widget.isRead;
super.initState();
}
// This function works but only after I hot refresh, otherwise it works but the rendering is wrong
Future<void> setAsRead() async {
if ( !_isRead ) {
try {
bool result = await ApiService().firebase.setNotificationAsRead(widget.id);
setState(() {
_isRead = result;
});
} catch (onError) {
ToastrService().error(context, onError.toString());
}
}
}
@override
Widget build(BuildContext context) {
// If I print all the widget. variables, the values are right. BUILD IS CALLED WHEN I CALL insertItem. After hot reload, build is called after initState as it is supposed to do
return Container(
child: GestureDetector(
onTap: this.setAsRead,
child: Card(
semanticContainer: true,
clipBehavior: Clip.antiAliasWithSaveLayer,
elevation: 2.0,
child: Container(
margin: EdgeInsets.symmetric(horizontal: 6.5.w),
padding: EdgeInsets.symmetric(vertical: 1.5.h),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// InitState is not called even in this child:
NotificationsTimestamp(timestamp: widget.createdAt),
_isRead ? Container() :
Container(
child: Icon(
SFSymbols.circle_fill,
size: 12.0.sp,
color: Styles.primary,
),
),
],
),
Container(
margin: EdgeInsets.only(top: 1.0.h),
child: AutoSizeText(
widget.message,
style: Styles.NotificationText,
maxLines: 4,
minFontSize: 11.0,
),
),
],
),
),
),
),
);
}
}
Somehow, when a new notification arrives, the animation works and the item is added with the correct widget.text, but _isRead and the createdAt are wrong! Am I doing something wrong?
Upvotes: 1
Views: 372