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 622 forks source link

OptimisticResult problem #1335

Open nihcal opened 1 year ago

nihcal commented 1 year ago

I need to understand how to avoid duplicating items in List. I have mutation called createMessage with optimistic result and update function. In ApolloClient, normally optimistic response should be replaced with actual data, but i got item pushing twice, first optimisticResult then actual message.

Here's my code:

// client.dart
static GraphQLClient initClient(String? accessToken) {
    final httpLink = HttpLink('$lambdasEndpoint/graphql');

    final authLink = AuthLink(
      getToken: () async => 'Bearer $accessToken',
    );

    final websocketLink = WebSocketLink(
      '${lambdasEndpoint?.replaceFirst('https', 'wss')}/graphql',
      config: SocketClientConfig(
        inactivityTimeout: const Duration(hours: 1),
        autoReconnect: true,
        initialPayload: {
          'authToken': accessToken,
        },
      ),
    );

    final link = Link.split(
      (request) => request.isSubscription,
      websocketLink,
      authLink.concat(httpLink),
    );

    final GraphQLClient client = GraphQLClient(
      cache: GraphQLCache(
        typePolicies: {
          'UserOutput': TypePolicy(
            keyFields: {
              'id': true,
            },
            fields: {
              'teams': FieldPolicy(
                merge: (existing, incoming, options) {
                  return incoming;
                },
              )
            },
          ),
          'RoomOutput2': TypePolicy(
            keyFields: {
              'id': true,
            },
            fields: {
              'operators': FieldPolicy(
                merge: (existing, incoming, options) {
                  return incoming;
                },
              )
            },
          ),
          'MessageOutput': TypePolicy(
            keyFields: {
              'messageId': true,
            },
            fields: {
              'messageText': FieldPolicy(
                read: (existing, options) {
                  return existing;
                },
              ),
            },
          ),
          'Query': TypePolicy(
            fields: {
              'getRoomsForMobile': FieldPolicy(
                keyArgs: ['status', 'channelsTypes', 'filter'],
                merge: (existing, incoming, options) {
                  return incoming;
                },
              ),
              'getChatRoomMessages': FieldPolicy(
                keyArgs: ['chatRoomId'],
                merge: (existing, incoming, options) {
                  return incoming;
                },
              ),
            },
          ),
        },
      ),
      link: link,
    );

    return client;
  }

// use_store_message.dart
typedef StoreFunction = void Function(
    Fragment$CoreMessageFields message, String chatRoomId);

StoreFunction useStoreMessage() {
  final client = useGraphQLClient();

  void storeMessage(Fragment$CoreMessageFields message, String chatRoomId) {
    final variables = Variables$Query$GetChatRoomMessages(
      chatRoomId: chatRoomId,
      fetchSize: 20,
    );

    final existing = client
        .readQuery$GetChatRoomMessages(
          variables: variables,
        )
        ?.getChatRoomMessages;

    if (existing != null) {
      final List<Fragment$CoreMessageFields> existingMessagesRefs =
          List<Fragment$CoreMessageFields>.from(
              existing.chatRoomMessages ?? []);

      if (existingMessagesRefs.any((m) => m.messageId == message.messageId)) {
        return;
      }

      final List<Fragment$CoreMessageFields> mergedMessages = [
        ...existingMessagesRefs,
        message,
      ];

      client.writeQuery$GetChatRoomMessages(
        data: Query$GetChatRoomMessages(
          getChatRoomMessages: Query$GetChatRoomMessages$getChatRoomMessages(
            chatRoomMessages: mergedMessages,
            roomId: existing.roomId,
            pagingState: existing.pagingState,
          ),
        ),
        variables: variables,
      );
    }
  }

  return storeMessage;
}

// messages.dart
final getChatRoomMessagesResult = useQuery$GetChatRoomMessages(
      Options$Query$GetChatRoomMessages(
        variables: Variables$Query$GetChatRoomMessages(
          chatRoomId: chatRoomId,
          fetchSize: 20,
          pagingState: "",
        ),
        fetchPolicy: FetchPolicy.cacheFirst,
      ),
    );

// messages_bar.dart
final createMessage = useMutation$CreateMessage(
      WidgetOptions$Mutation$CreateMessage(
        update: (cache, result) {
          final message = result?.parsedData?.createMessage;

          if (message != null) {
            storeMessage(message, chatRoomId);
          }
        },
      ),

final sendMessage = useCallback(() {
      if (user == null || room == null) return;

      final trimmedMessageText = textEditingController.text.trim();

      if (trimmedMessageText.isNotEmpty) {
        textEditingController.clear();

        createMessage.runMutation(
          variables: Variables$Mutation$CreateMessage(
            input: Input$CreateChatRoomMessageInput(
              chatRoomId: room.id,
              isClient: false,
              messageAuthorId: user.id,
              messageChannelId: room.channel.id,
              messageChannelType: room.channel.type,
              messageText: trimmedMessageText,
              messageContent: null,
            ),
          ),
          typedOptimisticResult: Mutation$CreateMessage(
            createMessage: Fragment$CoreMessageFields(
              channelId: room.channel.id,
              channelTypeName: room.channel.type,
              chatRoomId: room.id,
              chatRoomStatus: room.status.toString(),
              messageAuthorId: user.id,
              messageChannelId: room.channel.id,
              messageContent: null,
              messageCreatedDateTime: DateTime.now().toIso8601String(),
              messageDeletedDateTime: null,
              messageId: const Uuid().v4(),
              messageIsDelivered: false,
              messageIsRead: false,
              messageIsSent: false,
              messageText: trimmedMessageText,
              messageUpdatedDateTime: null,
            ),
          ),
        );
      }
    }, [user, room, textEditingController, createMessage.runMutation]);

dependencies: graphql_flutter: ^5.1.2 flutter_hooks: ^0.18.6 graphql_codegen: ^0.12.2 build_runner: ^2.3.3

nihcal commented 1 year ago

Can someone help with this issue?

vincenzopalazzo commented 1 year ago

This is a nice think to drive in, I currently do no have much time to look at the code, but I guess the code that implements the OptimisticResult will help you