React Native’de WebSocket kullanılarak bir chat uygulaması nasıl yapılır?

Zafer Ayan
11 min readApr 12, 2020

--

Karantina günleri yaşadığımız bu dönemde chat uygulamaları hayatımızda önemli bir rol oynuyor. Peki React Native ile kendi chat uygulamamızı nasıl geliştirebiliriz? Sorunun cevabını bu yazıda vermeye çalışacağım.

Öncelikle projeminizi oluşturalım ve boş haliyle çalıştıralım:

npx react-native init SampleRNChat --template react-native-template-typescript
cd SampleRNChat
npx react-native run-ios

Projemizi git’e ekleyelim ve vscode ile açalım

git init
git add .
git commit -m "First commit"
code .

Bir React Native uygulamasında diğer cihaz ile realtime haberleşme için websocket kullanmamız gerekiyor. Şimdi bu konuya değinelim.

Websocket kullanımı

React Native’de websocket kullanımı basit haliyle kullanımı aşağıdaki gibidir:

import React from 'react';const App = () => {
var ws = new WebSocket('wss://echo.websocket.org');
ws.onopen = () => {
const message = 'hello';
ws.send(message);
console.log(`Sent: ${message}`);
};
ws.onmessage = (e) => {
console.log(`Received: ${e.data}`);
};
ws.onerror = (e) => {
console.log(`Error: ${e.message}`);
};
ws.onclose = (e) => {
console.log(e.code, e.reason);
};
return <></>;
};
export default App;

Kodu açıklayacak olursak:

  • wss://echo.websocket.org: Örnek bir websocket servisidir. Gönderdiğiniz mesajı size geri döner.

Kodu çalıştırdığınızda uygulama hello mesajı gönderecek ve cevap olarak da hello mesajını geri alacaksınız:

Şimdi bir arayüz oluşturalım.

Arayüz oluşturma

Arayüz için daha önce yazdığım Redux ve TypeScript yazısındaki örnekte yer alan arayüz kodlarını kullanacağım.

import React, {useState, useEffect} from 'react';
import {
StyleSheet,
SafeAreaView,
FlatList,
View,
Text,
KeyboardAvoidingView,
TextInput,
TouchableOpacity,
} from 'react-native';
export interface Message {
user: string;
text: string;
timestamp: number;
}
export interface ChatState {
messages: Message[];
}
const emptyMessage: Message = {
user: 'zafer',
timestamp: new Date().getTime(),
text: '',
};
const ws = new WebSocket('wss://echo.websocket.org');const App = () => {
const [message, setMessage] = useState<Message>(emptyMessage);
const [chat, setChat] = useState<ChatState>({
messages: [],
});
useEffect(() => {
ws.onopen = () => {
console.log('Websocket opened.');
};
}, []);
ws.onmessage = (e) => {
console.log(`Received: ${e.data}`);
handleReceive(e.data);
};
ws.onerror = (e) => {
console.log(`Error: ${e.message}`);
};
ws.onclose = (e) => {
console.log(e.code, e.reason);
};
const handleReceive = (text: string) => {
const newChat = {...chat};
newChat.messages.push({...emptyMessage, user: 'cpu', text: text});
setChat(newChat);
};
const handleSend = () => {
console.log('Sent:' + message.text);
if (message.text === '') {
return;
}
ws.send(message.text);
const newChat = {...chat};
newChat.messages.push({...message});
setChat(newChat);
setMessage(emptyMessage);
};
const handleChangeText = (e: string) => {
setMessage({
text: e,
timestamp: new Date().getTime(),
user: 'zafer',
});
};
const formatTime = (timestamp: number) => {
const date = new Date(timestamp);
const hours = date.getHours();
const minutes = date.getMinutes();
const hoursText = hours < 10 ? `0${hours}` : hours;
const minutesText = minutes < 10 ? `0${minutes}` : minutes;
return `${hoursText}:${minutesText}`;
};
return (
<SafeAreaView style={styles.container}>
<FlatList
data={chat.messages}
keyExtractor={(item) => item.timestamp.toString()}
renderItem={({item}) => (
<View
style={{
...styles.messageContainer,
...(item.user !== 'zafer' ? styles.messageContainerReceived : {}),
}}>
<Text style={styles.messageText}>{item.text}</Text>
<Text style={styles.messageTime}>{formatTime(item.timestamp)}</Text>
</View>
)}
/>
<KeyboardAvoidingView
enabled={true}
behavior="padding"
style={styles.inputContainer}>
<TextInput
style={styles.textInput}
returnKeyType="send"
onChangeText={handleChangeText}
onSubmitEditing={handleSend}
value={message.text}
/>
<TouchableOpacity style={styles.sendButton} onPress={handleSend}>
<Text style={styles.sendButtonText}>Gönder</Text>
</TouchableOpacity>
</KeyboardAvoidingView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
margin: 10,
flex: 1,
justifyContent: 'space-between',
},
messageContainer: {
alignSelf: 'flex-end',
alignItems: 'flex-end',
padding: 10,
backgroundColor: '#1976d2',
borderRadius: 3,
marginBottom: 5,
flexDirection: 'row',
maxWidth: 300,
},
messageContainerReceived: {
alignSelf: 'flex-start',
alignItems: 'flex-start',
backgroundColor: '#00796b',
},
messageText: {
color: '#fff',
fontSize: 15,
marginEnd: 40,
},
messageTime: {
color: '#fff',
fontSize: 12,
opacity: 0.7,
marginStart: 10,
position: 'absolute',
end: 10,
bottom: 10,
},
inputContainer: {flexDirection: 'row', alignItems: 'center'},
textInput: {
flex: 1,
borderColor: '#448aff',
borderWidth: 1,
padding: 10,
borderRadius: 3,
marginBottom: 20,
},
sendButton: {paddingHorizontal: 10, marginBottom: 20},
sendButtonText: {color: '#448aff'},
});
export default App;

Kendi Websocket server’ımızı yapalım

echo.websocket.org servisi yerine gelen mesajı echo edecek olan kendi websocket servisimizi yazabiliriz. Bunun için server.js dosyasını oluşturalım ve içini aşağıdaki gibi dolduralım:

const WebSocket = require('ws');const wss = new WebSocket.Server({port: 8080}, () => {
console.log('Server started...');
});
const delay = 1000;
let ws;
wss.on('connection', (socket) => {
ws = socket;
console.log('Client connected...');
send('Uygulamaya Hoşgeldin');
ws.on('message', receive);
});
const receive = (msg) => {
console.log(`Received: ${msg}`);
setTimeout(() => send(msg), delay);
};
const send = (msg) => {
ws.send(msg);
console.log(`Sent: ${msg}`);
};

Kodu açıklayacak olursak:

  • new WebSocket.Server(): Belirlenen port numarası ile yayın yapılmasını sağlar.
  • const delay = 1000: Localhost üzerinde çalıştığımız için network gecikmesini simule etmek için ekledim. İsterseniz 0 verebilirsiniz.
  • wss.on(‘connection’): Bir client uygulama socket’e bağlandığında tetiklenir ve üzerinde işlem yapmak için bir socket nesnesi oluşturur.
  • ws.on(‘message’): WebSocket server’a mesaj geldiğinde bu metot karşılar.
  • const receive = (msg): Bu metodu, mesaj geldiğinde aynı mesajı geri döndürmek için kullanıyoruz. setTimeout ile 1 saniyelik bir gecikmeyi simüle edebiliyoruz.
  • const send = (): Bu metodu websocket üzerinden client’a mesaj gönderimi için generic bir metot olarak oluşturdum. Göndermeden önce loglama gibi işlemleri burada yapabileceğiz.
  • ws.send(msg): Socket üzerinden mesaj gönderimi sağlar.

Aşağıdaki komut ile server’ı çalıştırabilirsiniz:

node server.js

Çalıştırdığımız sunucuyu uygulamanın da kullanabilmesi için App.tsx’e gidip aşağıdaki gibi localhost url’ini kullanmamız yeterlidir:

const ws = new WebSocket('ws://localhost:8080');

Uygulama artık kendi websocket servisimizi kullanarak çalışacaktır ve sunucu kendisine gönderilen mesajı echo edecektir. Şimdi sunucu tarafında bir sıkıntı olduğunda veya client side’da bağlantı koptuğunda tekrar bağlanma olayı nasıl yapılıyor ona değinelim.

Bağlantı koptuğunda tekrar bağlanma özelliği

Bağlantı koptuğunda tekrar bağlanma işleminin gerçekleştirilebilmesi için, WebSocket nesnesinin kendi listener’ları ile birlikte yeniden oluşturulması (new edilmesi) gerekir. Bunun için WebSocket ile ilgili kısımları startWebSocket() adında yeni bir fonksiyon içerisine alalım. Ardından useEffect içinde bu oluşturduğumuz fonksiyonu çağıralım:

/* eslint-disable react-hooks/exhaustive-deps */
useEffect(() => {
startWebSocket();
}, []);
const startWebSocket = () => {
console.log('Websocket started.');
ws = new WebSocket(`ws://localhost:8080`);
ws.onmessage = (e) => {
console.log(`Received: ${e.data}`);
handleReceive(e.data);
};
ws.onclose = (e) => {
console.log('Reconnecting: ', e.message);
setTimeout(startWebSocket, 5000);
};
ws.onerror = (e) => {
console.log(`Error: ${e.message}`);
};
};

Kodu çalıştırıp sunucuyu kapattığınızda her 5 saniyede bir bağlanmaya çalışacaktır. Sunucuyu çalıştırdığınızda ise kaldığı yerden normal şekilde çalışmaya devam edecektir:

Şimdi mesajı sürekli echo etmesi yerine, websocket’i dinleyecek bir cihaz (android) daha bağlayalım ve iOS ile Android’in birbirleriyle mesajlaşmalarını sağlayalım.

Bir kullanıcıdan diğer kullanıcılara mesaj gönderimi

Bir kullanıcıdan diğer kullanıcılara mesaj gönderimini gerçekleştirmek için öncelikle server.js dosyasında client listesini tutacak bir değişkene ihtiyacımız var.

let clients = {};

Bir kullanıcıdan diğer kullanıcılara mesaj gönderirken, gönderen kişiye aynı mesajın tekrar geri echo edilmemesi için gönderim listesinden çıkarılması gerekir. Bu amaçla her bir kullanıcıya ait benzersiz bir id verilir. Bunun için socket header listesinde yer alan sec-websocket-key header’ı kullanılabilir. Bu header’da base64 ile kodlanmış bir veri vardır (Örnek başlıkSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== ). Bu değer ve mevcut socket nesnesi alınarak clients nesnesi için bir key-value oluşturulur. Bu sayede sonraki çağrımlarda clients nesnesinde ilgili socket key’e bakılara websocket nesnesine erişilir ve işlemler gerçekleştirilir.

Aşağıdaki gibi kullanımı onConnection durumuna ekleyelim.

wss.on('connection', (socket) => {
ws = socket;
const client = ws.upgradeReq.headers['sec-websocket-key'];
clients[client] = ws;

...
});

Mesaj geldiğinde de bu mesajın gönderen haricinde diğer kullanıcılara iletilmesi için broadcast isimli bir metot oluşturarak, clients nesnesi içerisinde gezen ve ilgili client’ın sender olup olmadığını kontrol eden metodu oluşturalım:

const broadcast = (msg, sender) => {
Object.keys(clients).map((client) => {
if (client === sender) return;
clients[client].send(msg);
console.log(`Sent: ${msg}, to ${client}`);
});
};

Ayrıca socket bağlantısı koptuğunda, o socket’e ait client’ı, clients listesinden silmemiz gerekiyor. Bunun için mesaj gönderiminde hatayı yakalayabiliriz. Yani send metodunun error callback’ini kullanabiliriz:

clients[client].send(msg, (error) => {
if (error) delete clients[client];
});

server.js son hali ile dosyası aşağıdaki gibi olacaktır:

const WebSocket = require('ws');const wss = new WebSocket.Server({port: 8080}, () => {
console.log('Server started...');
});
const delay = 1000;
let ws;
let clients = {};
wss.on('connection', (server) => {
ws = server;
const client = ws.upgradeReq.headers['sec-websocket-key'];
clients[client] = ws;
send('Uygulamaya Hoşgeldiniz', client);
ws.on('message', (msg, data) => receive(msg, data, client));
ws.on('close', (socket, number, reason) =>
console.log('Closed: ', client, socket, number, reason),
);
});
const send = (msg, client) => {
clients[client].send(msg, (error) => {
if (error) {
delete clients[client];
} else {
console.log(`Sent: ${msg}, to ${client}`);
}
});
};
const receive = (msg, data, sender) => {
console.log(`Received: ${msg}, from ${sender}`);
setTimeout(() => broadcast(msg, sender), delay);
};
const broadcast = (msg, sender) => {
Object.keys(clients).map((client) => {
if (client === sender) {
return;
}
send(msg, client);
});
};

Şimdi websocket üzerinden fotoğraf gönderimi yapalım. Ama öncelikle bunun için uygulamada fotoğraf seçme butonu ve arayüzünün yapılması gerekiyor. Bu amaçla image picker kütüphanesi ile nasıl fotoğraf seçip göndereceğiz ona değinelim.

ImagePicker kullanımı

React Native uygulamasında fotoğraf seçip göndermek için image picker kütüphanesini kullanabiliriz:

yarn add react-native-image-picker
npx pod-install

En basit hali ile kullanımı aşağıdaki gibi yer almaktadır. ImagePicker.showImagePicker() metodundan dönen response objesinde data (base64 string) ve uri (dosya path’i) olmak üzere iki ayrı özellik bulunur. data özelliği fotoğraf gönderiminde, uri ise fotoğraf görüntüleme için kullanılabilir:

import React, {useState, useEffect} from 'react';
import ImagePicker from 'react-native-image-picker';
import {SafeAreaView, Image} from 'react-native';
const App = () => {
const [imagePath, setImagePath] = useState<string>();
useEffect(() => {
const options = {
title: 'Fotoğraf seçiniz',
storageOptions: {
skipBackup: true,
path: 'images',
},
};
ImagePicker.showImagePicker(options, (response) => {
if (response.didCancel) {
console.log('Cancelled');
} else if (response.error) {
console.log('Error: ', response.error);
} else if (response.customButton) {
console.log('Tapped: ', response.customButton);
} else {
setImagePath(response.uri);
}
});
}, []);
return (
<SafeAreaView style={{flex: 1}}>
<Image style={{flex: 1}} source={{uri: imagePath}} />
</SafeAreaView>
);
};
export default App;

Uygulamanın işleyişi aşağıdaki gibidir. Öncelikle BottomSheet açılır. Choose from Library seçilir ve fotoğraflara gidilir. Fotoğraf’a tıklanıp seçildiğinde uygulama ekranına geri dönülerek ilgili fotoğraf görüntülenir:

Android tarafında ise uygulamada gerekli izinlerin sağlanabilmesi için AndroidManifest.xml dosyasında aşağıdaki izinleri vermeniz gereklidir:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Şimdi fotoğrafın base64 değerini alabildiğimize göre WebSocket üzerinden bu değerin gönderimine geçebiliriz.

WebSocket tarafında fotoğraf gönderimi

Metin gönderimi haricinde Base64 formatında görsel gönderimi için, Message sınıfına string tipinde image özelliğini ekleyelim ve text özelliğini nullable yapalım:

export interface Message {
user: string;
timestamp: number;
text?: string;
image?: string;
}

Ayrıca gönderim kısmında mesaj metni yerine mesaj objesinin tamamını gönderecek şekilde değiştirelim:

ws.send(message.text)
yerine
ws.send(messsage)

Bir de fotoğraf gönderimi için bir buton yapalım ve imagePicker’ı entegre edelim. App.tsx’in son hali aşağıdaki gibi olacaktır:

/* eslint-disable react-native/no-inline-styles */
/* eslint-disable react-hooks/exhaustive-deps */
import React, {useState, useEffect} from 'react';
import {
StyleSheet,
SafeAreaView,
FlatList,
View,
Text,
KeyboardAvoidingView,
TextInput,
TouchableOpacity,
Platform,
Image,
} from 'react-native';
import ImagePicker from 'react-native-image-picker';
export interface Message {
user: string;
timestamp: number;
text?: string;
image?: string;
}
export interface ChatState {
messages: Message[];
}
const emptyMessage: Message = {
user: 'me',
timestamp: new Date().getTime(),
text: '',
};
// Android'de 10.2.2.2 IP'si üzerinden localhost'a erişiliyor
const domain = Platform.OS === 'ios' ? 'localhost' : '10.0.2.2';
let ws: WebSocket;const App = () => {
const [message, setMessage] = useState<Message>(emptyMessage);
const [chat, setChat] = useState<ChatState>({
messages: [],
});
useEffect(() => {
startWebSocket();
}, []);
const startWebSocket = () => {
console.log('Websocket started.');
ws = new WebSocket(`ws://${domain}:8080`);
ws.onmessage = (e) => {
console.log(`Received: ${e.data}`);
var msg = JSON.parse(e.data);
console.log(msg);
handleReceive(msg);
};
ws.onclose = (e) => {
console.log('Reconnecting: ', e.message);
setTimeout(startWebSocket, 5000);
};
ws.onerror = (e) => {
console.log(`Error: ${e.message}`);
};
};
const handleReceive = (receivedMsg: Message) => {
const newChat = {...chat};
const msg = {...receivedMsg, user: 'otherUser'};
newChat.messages.push(msg);
console.log(newChat.messages);
setChat(newChat);
};
const handleSend = () => {
console.log('Sent: ' + message);
if (message.text === '') {
return;
}
ws.send(JSON.stringify(message));
const newChat = {...chat};
newChat.messages.push({...message});
setChat(newChat);
setMessage(emptyMessage);
};
const handlePickAndSendImage = () => {
const options = {
title: 'Fotoğraf seçiniz',
storageOptions: {
skipBackup: true,
path: 'images',
},
};
ImagePicker.showImagePicker(options, (response) => {
if (response.didCancel) {
console.log('Cancelled');
} else if (response.error) {
console.log('Error: ', response.error);
} else if (response.customButton) {
console.log('Tapped: ', response.customButton);
} else {
const dataMsg = {...emptyMessage, image: response.data};
ws.send(JSON.stringify(dataMsg));
console.log(response.uri);
const msg = {
user: 'me',
timestamp: new Date().getTime(),
image: response.uri,
};
const newChat = {...chat};
newChat.messages.push({...msg});
setChat(newChat);
setMessage(emptyMessage);
}
});
};
const handleChangeText = (e: string) => {
setMessage({
text: e,
timestamp: new Date().getTime(),
user: 'me',
});
};
const formatTime = (timestamp: number) => {
const date = new Date(timestamp);
const hours = date.getHours();
const minutes = date.getMinutes();
const hoursText = hours < 10 ? `0${hours}` : hours;
const minutesText = minutes < 10 ? `0${minutes}` : minutes;
return `${hoursText}:${minutesText}`;
};
return (
<SafeAreaView style={styles.container}>
<FlatList
data={chat.messages}
keyExtractor={(item) => item.timestamp.toString()}
renderItem={({item}) => (
<View
style={{
...styles.messageContainer,
...(item.user !== 'me' ? styles.messageContainerReceived : {}),
flex: 1,
}}>
{item.image && (
<Image style={styles.image} source={{uri: item.image}} />
)}
{item.text && <Text style={styles.messageText}>{item.text}</Text>}
<Text style={styles.messageTime}>{formatTime(item.timestamp)}</Text>
</View>
)}
/>
<KeyboardAvoidingView
enabled={true}
{...(Platform.OS === 'ios' && {behavior: 'padding'})}
style={styles.inputContainer}>
<TextInput
style={styles.textInput}
onChangeText={handleChangeText}
onSubmitEditing={handleSend}
value={message.text}
returnKeyType="send"
/>
<TouchableOpacity
style={styles.sendButton}
onPress={handlePickAndSendImage}>
<Image
style={styles.imageButton}
source={require('./img/photo.png')}
/>
</TouchableOpacity>
<TouchableOpacity
style={styles.sendButton}
onPress={() =>
message.text!.length > 0 ? handleSend() : console.log()
}>
<Text style={styles.sendButtonText}>Gönder</Text>
</TouchableOpacity>
</KeyboardAvoidingView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
margin: 10,
flex: 1,
justifyContent: 'space-between',
},
messageContainer: {
alignSelf: 'flex-end',
alignItems: 'flex-end',
padding: 5,
backgroundColor: '#1976d2',
borderRadius: 3,
marginBottom: 5,
flexDirection: 'row',
maxWidth: 300,
},
messageContainerReceived: {
alignSelf: 'flex-start',
alignItems: 'flex-start',
backgroundColor: '#00796b',
},
messageText: {
color: '#fff',
fontSize: 15,
marginEnd: 40,
padding: 5,
},
messageTime: {
color: '#fff',
fontSize: 12,
opacity: 0.7,
marginStart: 10,
position: 'absolute',
end: 10,
bottom: 10,
},
inputContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
textInput: {
flex: 1,
borderColor: '#448aff',
borderWidth: 1,
padding: 10,
borderRadius: 3,
marginBottom: Platform.OS === 'ios' ? 20 : 0,
},
sendButton: {
paddingStart: 10,
marginBottom: Platform.OS === 'ios' ? 20 : 0,
},
imageButton: {
width: 24,
height: 24,
tintColor: '#448aff',
},
image: {
height: 100,
flex: 1,
},
sendButtonText: {color: '#448aff'},
});
export default App;

Bu arada server.js dosyasında da message nesnesini göndermek için JSON stringify ve parse işlemlerini aşağıdaki gibi gerçekleştirmek gerekiyor:

const WebSocket = require('ws');
const Cloudinary = require('cloudinary');
const wss = new WebSocket.Server({port: 8080}, () => {
console.log('Server started...');
});
let ws;
let clients = {};
wss.on('connection', (server) => {
ws = server;
const client = ws.upgradeReq.headers['sec-websocket-key'];
clients[client] = ws;
ws.on('message', (msg, data) => receive(msg, data, client));
ws.on('close', (socket, number, reason) =>
console.log('Closed: ', client, socket, number, reason),
);
});
const send = (msg, client) => {
console.log('Sending: ', msg);
clients[client].send(JSON.stringify(msg), (error) => {
if (error) {
delete clients[client];
} else {
console.log(`Sent: ${msg}, to ${client}`);
}
});
};
const receive = (msg, data, sender) => {
console.log(`Received: ${msg.substring(0, 500)}, from ${sender}`);
broadcast(msg, sender);
};
const broadcast = (msg, sender) => {
msg = JSON.parse(msg);
Object.keys(clients).map((client) => {
if (client === sender) {
return;
} else if (msg.image !== undefined) {
msg.text = undefined;
msg.timestamp = new Date().getTime();
send(msg, client);
} else {
send(msg, client);
}
});
};

Farkedeceğiniz üzere fotoğraf gönderilebiliyor fakat alıcı tarafta render edilemiyor. Bunun nedeni React Native 0.62.2 sürümünde Image bileşeni için base64 desteğinde problem olmasıdır. Bunun için Server tarafında fotoğrafı bir yere upload ederek link döndürebiliriz. Cloudinary provider’ı iş görecektir.

Cloudinary ile fotoğraf yükleme

cloudinary.com sitesine giderek hesap açıp kullanmaya başlayabilirsiniz. API_KEY’siz kullanımda ise preset oluşturmanız gerekiyor. Buradan preset oluşturabilirsiniz.

Sonrasında ise server.js dosyasında broadcast metodunda aşağıdaki değişikliği yapmak işinizi görecektir:

const broadcast = (msg, sender) => {
msg = JSON.parse(msg);
Object.keys(clients).map((client) => {
if (client === sender) {
return;
} else if (msg.image !== undefined) {
Cloudinary.v2.uploader.unsigned_upload(
`data:image/jpeg;base64,${msg.image}`,
'myPreset',
{cloud_name: 'zafer'},
(_err, result) => {
console.log('Uploaded URL: ' + result.url);
msg.image = result.url;
msg.text = undefined;
msg.timestamp = new Date().getTime();
send(msg, client);
},
);

} else {
send(msg, client);
}
});
};

Uygulamayı çalıştırdığınızda fotoğraf gönderiminin de başarılı bir şekilde çalıştığını görebilirsiniz:

Sonuç Olarak

Websocket ile chat uygulaması yapmak oldukça kolay. Siz de buradaki yönergeleri izleyerek kendi chat uygulamanızı yapabilirsiniz.

Projenin bitmiş halini react-native-chat-sample reposunda bulabilirsiniz. Bu yazı hakkında soru ve görüşlerinizi aşağıdaki yorumlar kısmından yazabilirsiniz. Bana destek vermek için alkış simgesine tıklayabilirsiniz. Sonraki yazımda görüşmek üzere…

--

--