g.imtech
g.imtech

Reputation: 91

[Flutter][AnimatedList] after "insertItem" the new element added to the list does not call "initState" but only "build"

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

Answers (0)

Related Questions