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 updated variables? #1336

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.

I am running into a few issues that lead me to believe that I should not be getting the SubscriptionOptions based off of the state variables and instead somehow be doing this in an invariant way, but that doesn't seem to make sense because how would the server then know what ID and Time to use to return results?

Help?

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));
                orderedMeetings = orderedMeetings.reversed.toList();

                Future.delayed(Duration.zero, () async {
                  setState(() {
                    lastMessageTime = orderedMeetings[0].lastMessageCreatedAt;
                    lastMessageID = orderedMeetings[0].messages[0].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,
                            );
                          }),
                    ],
                  ),
                );
              });
        },
      ),
    );
  }
}