zino-hofmann / graphql-flutter

A GraphQL client for Flutter, bringing all the features from a modern GraphQL client to one easy to use package.
https://zino-hofmann.github.io/graphql-flutter
MIT License
3.25k stars 623 forks source link

How do I implement subscriptions that require variables to update? #1334

Closed cinjon closed 1 year ago

cinjon commented 1 year ago

My code is below. What I am trying to do is subscribe to a graphql stream and update the lastMessageTime and lastMessageID after each time I get back messages. I then send those to the server in the stream in order to get new messages only.

The issue that I am running into is the following bug. I believe this is being caused by setState happening in the ResultsAccumulator but I don't see a way to get around that because I won't have the lastMessageTime or lastMessageID back at any other time...

Help?

flutter: ----------------FIREBASE CRASHLYTICS----------------
flutter: RangeError (index): Invalid value: Not in inclusive range 0..11: -1
flutter:
#0      List.[] (dart:core-patch/growable_array.dart:264:36)
#1      _MeetingsScreenState.build.<anonymous closure>.<anonymous closure>.<anonymous closure>.<anonymous closure>
meetings.dart:169
#2      State.setState
framework.dart:1139
#3      _MeetingsScreenState.build.<anonymous closure>.<anonymous closure>.<anonymous closure>
meetings.dart:166
#4      new Future.delayed.<anonymous closure> (dart:async/future.dart:427:39)
#5      Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:18:15)
#6      _Timer._runTimers (dart:isolate-patch/timer_impl.dart:398:19)
#7      _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:429:5)
#8      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:189:12)
class MeetingsScreen extends StatefulWidget {
  final String userId;

  const MeetingsScreen(
      {super.key,
      required this.userId});

  @override
  State<MeetingsScreen> createState() => _MeetingsScreenState();
}

class _MeetingsScreenState extends State<MeetingsScreen> {
  Map<String, MeetingModel> meetings = {};
  DateTime lastMessageTime = DateTime.parse("2020-01-01");
  String lastMessageID = "=0=";

  SubscriptionOptions getSubscriptionOptions(
      DateTime lastMessageTime, String lastMessageID) {
    return SubscriptionOptions(document: gql(r'''
        subscription messages($uid: ID!, $lastMessageTime: Date!, $lastMessageID: ID!) {
          messages(uid: $uid, lastMessageTime: $lastMessageTime, lastMessageID: $lastMessageID) {
            message {
              id
              createdAt
              content
              user {
                id
              }
            }
            meetingID
          }
        }
      '''), variables: {
      'uid': widget.userId,
      'lastMessageTime': lastMessageTime.toIso8601String(),
      'lastMessageID': lastMessageID,
    });
  }

  Map<String, MeetingModel> buildMeetingsFromSubscriptionMessages(
      List<Map<String, dynamic>?>? results) {
    // Do a bunch of stuff here to return a new set of meetings with the new messages.
    return meetings;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Subscription(
        options: getSubscriptionOptions(lastMessageTime, lastMessageID),
        builder: (result) {
          if (result.hasException) {
            return Text("Error occurred: " + result.exception.toString());
          }

          if (result.isLoading) {
            return Center(
              child: const CircularProgressIndicator(),
            );
          }

          return ResultAccumulator.appendUniqueEntries(
              latest: result.data,
              builder: (context, {results}) {
                Map<String, MeetingModel> meetings_ =
                    buildMeetingsFromSubscriptionMessages(results);
                List<MeetingModel> orderedMeetings = meetings_.values.toList();
                orderedMeetings.sort((a, b) =>
                    a.lastMessageCreatedAt.compareTo(b.lastMessageCreatedAt));

                Future.delayed(Duration.zero, () async {
                  setState(() {
                    lastMessageTime = orderedMeetings[0].lastMessageCreatedAt;
                    lastMessageID = orderedMeetings[0].messages[-1].messageId;
                    meetings = meetings_;
                  });
                });

                return SingleChildScrollView(
                  physics: const BouncingScrollPhysics(),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: <Widget>[
                      ListView.builder(
                          itemCount: orderedMeetings.length,
                          shrinkWrap: true,
                          padding: const EdgeInsets.only(top: 16),
                          physics: const NeverScrollableScrollPhysics(),
                          itemBuilder: (context, index) {
                            final meeting = orderedMeetings[index];
                            return MeetingList(
                              meeting: meeting,
                              userId: widget.userId,
                            );
                          }),
                    ],
                  ),
                );
              });
        },
      ),
    );
  }
}
cinjon commented 1 year ago

Nvm, it was because I was indexing with -1.