Mobile (Flutter)
A minimal client-side flow for messaging in a Flutter app. This mirrors the web clients (same GraphQL API, same WebSocket protocol, same token), it's just a different client on the same backend described in Implementation. Snippets below are intentionally minimal (illustrative, not production-ready). Treat exact APIs as dependent on whichever GraphQL/WebSocket package version you land on.
What's Needed
- A GraphQL HTTP client for queries/mutations (
createConversation,sendMessage,moderateConversation, ...) - A WebSocket client for subscriptions (
messageAdded,notificationReceived), using the same bearer token already obtained from sign-in - A multipart HTTP upload for attachments, ahead of sending a message that references them
Reuse whatever graphql/graphql_flutter-style package the rest of the app already uses for HTTP; the only messaging-specific piece is the subscription handshake.
Subscribing to a Conversation
The token goes in the connection_init payload, not a header, the same handshake described in Implementation:
final channel = WebSocketChannel.connect(
Uri.parse('wss://api.travo-mate.online/cargo/graphql'),
protocols: ['graphql-transport-ws'],
);
channel.sink.add(jsonEncode({
'type': 'connection_init',
'payload': {'authorization': 'Bearer $jwt'},
}));
channel.stream.listen((raw) {
final msg = jsonDecode(raw);
if (msg['type'] == 'connection_ack') {
channel.sink.add(jsonEncode({
'id': '1',
'type': 'subscribe',
'payload': {
'query': 'subscription(\$id: ID!) { messageAdded(conversationId: \$id) { id text senderId } }',
'variables': {'id': conversationId},
},
}));
} else if (msg['type'] == 'next') {
onNewMessage(msg['payload']['data']['messageAdded']);
}
});
Sending a Message
A plain GraphQL mutation over HTTP, like any other write in the app:
await graphQLClient.mutate(MutationOptions(
document: gql(r'''
mutation($id: ID!, $text: String, $attachmentIds: [ID!]) {
sendMessage(conversationId: $id, text: $text, attachmentIds: $attachmentIds) { id }
}
'''),
variables: {'id': conversationId, 'text': text, 'attachmentIds': attachmentIds},
));
Uploading an Attachment
Upload first, then include the returned id on sendMessage:
final request = http.MultipartRequest('POST', Uri.parse('https://api.travo-mate.online/cargo/media'))
..headers['Authorization'] = 'Bearer $jwt'
..files.add(await http.MultipartFile.fromPath('file', filePath));
final response = await request.send();
final body = jsonDecode(await response.stream.bytesToString());
final attachmentId = body['id'];
Render an image attachment with the token as a query parameter, the same fallback the web clients use since a request for image bytes can't carry a custom header:
Image.network('https://api.travo-mate.online/cargo/media/$attachmentId?token=$jwt')
Notifications
Same connection, same handshake, a second subscription. Most apps open this once at app-start rather than per-screen:
channel.sink.add(jsonEncode({
'id': '2',
'type': 'subscribe',
'payload': {
'query': 'subscription { notificationReceived { id type text relatedConversationId } }',
},
}));
Use relatedConversationId to jump straight to the right conversation when a notification is tapped.
Not Mobile-Specific
Everything else (permissions, conversation/message shape, moderation actions, what triggers a notification) is identical to the web clients and covered in Implementation and Mediation & Flagging. There's nothing mobile-only in the messaging model itself.