I have react native app with chat in it. And backend with Django rest framework and django channels.
consumers.py
import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
from .models import User, Connection, Message
import logging
from .serializers import (
MessageSerializer
)
from app.serializers import (
UserSerializer,
)
logger = logging.getLogger(__name__)
class ChatConsumer(WebsocketConsumer):
def connect(self):
user = self.scope['user']
self.accept()
if not user.is_authenticated:
return
self.username = user.username
# Join user to group
async_to_sync(self.channel_layer.group_add)(
self.username, self.channel_name
)
def disconnect(self, close_code):
async_to_sync(self.channel_layer.group_discard)(
self.username, self.channel_name
)
def receive(self, text_data):
data = json.loads(text_data)
data_source = data.get('source')
self.send(text_data=json.dumps({"message": data.get('message')}))
if data_source == 'message.send':
user = self.scope['user']
connectionId = data.get('connectionId')
message_text = data.get('message')
receiver = User.objects.get(username=data.get('user_receiver'))
try:
connection = Connection.objects.get(id=connectionId)
except Connection.DoesNotExist:
connection = Connection.objects.create(
sender=user,
receiver=receiver,
)
return
message = Message.objects.create(
connection=connection,
user=user,
text=message_text
)
# Get recipient friend
recipient = connection.sender
if connection.sender == user:
recipient = connection.receiver
# Send new message back to sender
serialized_message = MessageSerializer(
message,
context={
'user': user
}
)
serialized_friend = UserSerializer(recipient)
data = {
'message': serialized_message.data,
'friend': serialized_friend.data
}
self.send_group(user.username, 'message.send', data)
# Send new message to receiver
serialized_message = MessageSerializer(
message,
context={
'user': recipient
}
)
serialized_friend = UserSerializer(user)
data = {
'message': serialized_message.data,
'friend': serialized_friend.data
}
self.send_group(recipient.username, 'message.send', data)
elif data_source == 'message.list':
user = self.scope['user']
connectionId = data.get('connectionId')
try:
connection = Connection.objects.get(id=connectionId)
except Connection.DoesNotExist:
print('Error: couldnt find connection')
return
messages = Message.objects.filter(
connection=connection
).order_by('-created')
serialized_messages = MessageSerializer(
messages,
context={'user': user},
many=True
)
recipient = connection.sender
if connection.sender == user:
recipient = connection.receiver
serialized_friend = UserSerializer(recipient)
data = {
'messages': serialized_messages.data if messages.exists() else [],
}
self.send_group(user.username, 'message.list', data)
def send_group(self, group, source, data):
response = {
'type': 'broadcast_group',
'source': source,
'data': data
}
async_to_sync(self.channel_layer.group_send)(
group, response
)
def broadcast_group(self, data):
'''
data:
- type: 'broadcast_group'
- source: where it originated from
- data: what ever you want to send as a dict
'''
print('Broadcasting to group:', data)
'''
return data:
- source: where it originated from
- data: what ever you want to send as a dict
'''
self.send(text_data=json.dumps(data))
MessagesDmScreen.js:
import React, { useEffect, useState } from 'react';
import { GiftedChat } from 'react-native-gifted-chat';
import { View,Text, TextInput, TouchableOpacity, Platform } from 'react-native';
import { useSelector } from 'react-redux';
import uuid from 'react-native-uuid';
export const MessagesDmScreen = () => {
const user = useSelector(state => state.auth);
const [messages, setMessages] = useState([]);
const [refresh, setRefresh] = useState('not refreshed');
const [inputText, setInputText] = useState('');
const socket = new WebSocket(`ws://127.0.0.1:8000/chat/?token=${user.token}`);
useEffect(() => {
socket.addEventListener('open', () => {
// console.log(user.user.id,'WebSocket connection opened',new Date());
socket.send(
JSON.stringify({
source: 'message.list',
connectionId: 1,
})
);
});
}, []);
socket.onmessage = (event) => {
// Handle incoming messages from the server
const data = JSON.parse(event.data);
// Check the type of message
if (data.source === 'message.list' && data.data && data.data.messages) {
const receivedMessages = data.data.messages.map(msg => ({
_id: msg._id,
text: msg.text,
createdAt: new Date(msg.created),
user: {
_id: msg.user._id,
name: msg.user.username,
avatar: `http://127.0.0.1${msg.user.profile_image}`
}
}));
// Filter out duplicate messages based on their ID
const uniqueMessages = receivedMessages.filter(msg => !messages.find(existingMsg => existingMsg._id === msg._id));
console.log(uniqueMessages);
// Add unique messages to the state
setMessages(prevMessages => [...prevMessages, ...uniqueMessages]);
} else if (data.source === 'message.send' && data.data && data.data.message) {
const sentMessage = data.data.message;
// console.log(sentMessage);
// Check if the message ID already exists in the state
if (!messages.find(existingMsg => existingMsg._id === sentMessage._id)) {
// Add the new message to the state
const newMessage = {
_id: sentMessage._id,
text: sentMessage.text,
createdAt: new Date(sentMessage.created),
user: {
_id: sentMessage.user._id,
name: sentMessage.user.username,
avatar: `http://127.0.0.1${sentMessage.user.profile_image}`
}
};
setMessages(prevMessages => [...prevMessages, newMessage]);
}
} else {
// Handle other types of messages or unexpected formats
// console.log('Received unexpected message format:', data);
}
};
const onSend = (newMessages) => {
const messageText = newMessages[0].text;
if (messageText.trim() === '') {
return;
}
const newMessage = {
_id: uuid.v4(),
text: messageText,
createdAt: new Date(),
user: {
_id: user.user.id,
name: user.user.username,
avatar: `http://127.0.0.1${user.user.profile_image}`
},
};
setMessages((prevMessages) => GiftedChat.append(prevMessages, newMessage));
socket.send(
JSON.stringify({
source: 'message.send',
connectionId: 1,
user_receiver:user.user.username == 'DiasOralbekov' ? 'Dias' : 'DiasOralbekov',
message: messageText,
})
);
};
return (
<View style={{ flex: 1,marginBottom:100 }}>
<GiftedChat
messages={messages}
onSend={onSend}
isAnimated
user={{
_id: user.user.id,
}}
/>
</View>
);
};
Problem that when i send message Django Channels broadCasting it 4 times if i have 2 connected phone in chat websocket. But when I fetch messages like in socket.addEventListener('open') it works. And because of that i receive multiple messages and i cant set state messages right and i receive error on 2 connected to chat phones:
ERROR Warning: Encountered two children with the same key, `224`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.
224 is id of message i sent.
Main goal is to make chat working, if you have other better ways to do chat functionality please let me know about it