We’re going to create a React Native application that will act as a Websockets client and communicate with a remote server. WebSockets is a popular, simple and robust web communication protocol enabling real-time communication between client and server.
Hardware
- Android device
- Computer for programming
- USB cable to connect Android device to PC
React Native project configuration
To communicate via Websocket, we’re going to use the Websocket library, react-use-websocket
npm install --save react-use-websocket
Utilisation de la librairie
From the library, we import the objects and functions we are interested in
import useWebSocket, { ReadyState } from 'react-use-websocket';
- ReadyState server connection status
- useWebScoket to initialize a websockets connection
We then create a functional component with the desired states
const [ipAddress, setIpAddress] = useState(''); const [ipServer, setIpServer] = useState('ws://192.168.1.52:8765'); const [messageText, setMessageText] = useState(""); const [messageHistory, setMessageHistory] = useState([]); const { sendMessage, sendJsonMessage, lastMessage, lastJsonMessage, readyState } = useWebSocket(ipServer); const connectionStatus = { [ReadyState.CONNECTING]: 'Connecting', [ReadyState.OPEN]: 'Open', [ReadyState.CLOSING]: 'Closing', [ReadyState.CLOSED]: 'Closed', [ReadyState.UNINSTANTIATED]: 'Uninstantiated', }[readyState]; const paramCmd = { type: 'setParam', param1: 30, param2: 2.3, }
In the states we retrieve the functions and states of useWebSokcet
- sendMessage the function for sending messages in String format
- sendJsonMessage for sending messages in JSON format
- lastMessage contains the server response in String format
- lastJsonMessage contains the last server message in JSON format
- readyState contains the connection state
We also define a constant in Json paramCmd format.
We use a useEffect hook to retrieve the device’s IP address and manage the messages received from the server.
useEffect(() => { const fetchIpAddress = async () => { const ip = await NetworkInfo.getIPV4Address(); setIpAddress(ip); console.log("ip adresses ; ", ip) }; fetchIpAddress(); if (lastMessage !== null) { setMessageHistory((prev) => prev.concat(lastMessage)); } if (lastJsonMessage !== null) { console.log(JSON.stringify(lastJsonMessage)); } return () => { }; }, [lastMessage, setMessageHistory,lastJsonMessage]);
Finally, we create the application rendering with the following elements
return ( <View style={styles.mainBody}> <Text style={styles.mainTitle}> AC Websocket Terminal </Text> <ScrollView> <View style={styles.deviceItem}> <View style={{flex:2}}> <Text style={styles.deviceName}>{ipAddress}</Text> <Text style={styles.deviceInfo}>{connectionStatus}</Text> </View> <TouchableOpacity onPress={() => sendJSON()} disabled={readyState !== ReadyState.OPEN} style={styles.deviceButton}> <Text style={styles.buttonText}> Send JSON </Text> </TouchableOpacity> </View> <View style={styles.inputBar}> <TextInput style={styles.textInput} placeholder="Enter a message" value={messageText} onChangeText={(text) => setMessageText(text) } /> <TouchableOpacity onPress={() => sendMessage(messageText)} disabled={readyState !== ReadyState.OPEN} style={[styles.sendButton]}> <Text style={styles.buttonText}> SEND </Text> </TouchableOpacity> </View> <TextInput placeholder="Server IP" onChangeText={setIpServer} value={ipServer} /> <View style={{flex:1,minHeight:200}}> <Text>Received Message:</Text> <ScrollView style={styles.textOutput}> {lastMessage ? <Text>last message : {lastMessage.data}</Text> : null} {messageHistory.map((message, idx) => ( <Text key={idx}>{message ? message.data : null}</Text> ))} </ScrollView> </View> </ScrollView> </View> );
Creating a WebSockets server with Python
To test our application, we create a websockets server on the PC
#!/usr/bin/env python # python3 -m pip install websockets import json import asyncio from websockets.server import serve def getIpAddress(): import socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(("8.8.8.8", 80)) ipaddress = s.getsockname()[0] s.close() return ipaddress ipaddress = getIpAddress() port = 8765 async def echo(websocket): async for message in websocket: print("received from {}:{} : ".format(websocket.remote_address[0],websocket.remote_address[1]) + message) if('{' not in message): await websocket.send(message) else: request = json.loads(message) answer = {} if(request['type'] == 'setParam'): answer['type'] = request['type'] if(request['param1']<100 and request['param2']>1.2): answer['valid'] = True for key, val in request.items(): print("\t"+key+": ", val) else: answer['valid'] = False else: answer['type'] = 'unknown' answer['valid'] = False await websocket.send(json.dumps(answer)) async def main(): print("Server is activated on ws://{}:{}".format(ipaddress,port)) #async with serve(echo, "localhost", 8765): async with serve(echo, "0.0.0.0", port): await asyncio.Future() # run forever asyncio.run(main())
Results
Thanks to this application, we can send Strings and JSON to the server and display its responses.
Complete code for WebSockets communication with React Native
/** * https://reactnative.dev/docs/network * https://www.npmjs.com/package/react-use-websocket * https://github.com/robtaussig/react-use-websocket * test on python * https://websockets.readthedocs.io/en/stable/ */ import React, {useState, useEffect, useCallback} from 'react'; import { View, ScrollView, Text, TextInput, TouchableOpacity, StyleSheet} from 'react-native'; import { NetworkInfo } from 'react-native-network-info' import useWebSocket, { ReadyState } from 'react-use-websocket'; const WebsocketTerminal = () => { const [ipAddress, setIpAddress] = useState(''); const [ipServer, setIpServer] = useState('ws://192.168.1.52:8765'); const [messageText, setMessageText] = useState(""); const [messageHistory, setMessageHistory] = useState([]); const { sendMessage, sendJsonMessage, lastMessage, lastJsonMessage, readyState } = useWebSocket(ipServer); useEffect(() => { const fetchIpAddress = async () => { const ip = await NetworkInfo.getIPV4Address(); setIpAddress(ip); console.log("ip adresses ; ", ip) }; fetchIpAddress(); if (lastMessage !== null) { setMessageHistory((prev) => prev.concat(lastMessage)); } if (lastJsonMessage !== null) { console.log(JSON.stringify(lastJsonMessage)); } return () => { }; }, [lastMessage, setMessageHistory,lastJsonMessage]); const connectionStatus = { [ReadyState.CONNECTING]: 'Connecting', [ReadyState.OPEN]: 'Open', [ReadyState.CLOSING]: 'Closing', [ReadyState.CLOSED]: 'Closed', [ReadyState.UNINSTANTIATED]: 'Uninstantiated', }[readyState]; const sendJSON = () => { const paramCmd = { type: 'setParam', param1: 30, param2: 2.3, } sendJsonMessage(paramCmd); } return ( <View style={styles.mainBody}> <Text style={styles.mainTitle}> AC Websocket Terminal </Text> <ScrollView> <View style={styles.deviceItem}> <View style={{flex:2}}> <Text style={styles.deviceName}>{ipAddress}</Text> <Text style={styles.deviceInfo}>{connectionStatus}</Text> </View> <TouchableOpacity onPress={() => sendJSON()} disabled={readyState !== ReadyState.OPEN} style={styles.deviceButton}> <Text style={styles.buttonText}> Send JSON </Text> </TouchableOpacity> </View> <View style={styles.inputBar}> <TextInput style={styles.textInput} placeholder="Enter a message" value={messageText} onChangeText={(text) => setMessageText(text) } /> <TouchableOpacity onPress={() => sendMessage(messageText)} disabled={readyState !== ReadyState.OPEN} style={[styles.sendButton]}> <Text style={styles.buttonText}> SEND </Text> </TouchableOpacity> </View> <TextInput placeholder="Server IP" onChangeText={setIpServer} value={ipServer} /> <View style={{flex:1,minHeight:200}}> <Text>Received Message:</Text> <ScrollView style={styles.textOutput}> {lastMessage ? <Text>last message : {lastMessage.data}</Text> : null} {messageHistory.map((message, idx) => ( <Text key={idx}>{message ? message.data : null}</Text> ))} </ScrollView> </View> </ScrollView> </View> ); } export default WebsocketTerminal; let BACKGROUND_COLOR = "#161616"; //191A19 let BUTTON_COLOR = "#346751"; //1E5128 let ERROR_COLOR = "#C84B31"; //4E9F3D let TEXT_COLOR = "#ECDBBA"; //D8E9A8 var styles = StyleSheet.create({ mainBody: { flex: 1, justifyContent: 'center', backgroundColor: BACKGROUND_COLOR}, mainTitle:{ color: TEXT_COLOR, fontSize: 30, textAlign: 'center', borderBottomWidth: 2, borderBottomColor: ERROR_COLOR, }, backgroundVideo: { borderWidth: 2, borderColor: 'red', position: 'absolute', top: 0, left: 0, bottom: 0, right: 0, }, webVideo: { borderWidth: 2, borderColor: 'green', position: 'absolute', top: 0, left: 0, bottom: 0, right: 0, }, buttonText: { color: TEXT_COLOR, fontWeight: 'bold', fontSize: 12, textAlign: 'center', textAlignVertical: 'center', }, sendButton: { backgroundColor: BUTTON_COLOR, padding: 15, borderRadius: 15, margin: 2, paddingHorizontal: 20, }, deviceItem: { flexDirection: 'row', flex: 3, marginBottom: 2, }, deviceName: { fontSize: 14, fontWeight: 'bold', }, deviceInfo: { fontSize: 8, }, deviceButton: { backgroundColor: '#2196F3', padding: 10, borderRadius: 10, margin: 2, paddingHorizontal: 20, }, inputBar:{ flexDirection: 'row', justifyContent: 'space-between', margin: 5, }, textInput:{ backgroundColor: '#888888', margin: 2, borderRadius: 15, flex:3, }, textOutput:{ backgroundColor: '#333333', margin: 10, borderRadius: 2, borderWidth: 1, borderColor: '#EEEEEE', textAlignVertical: 'top', } });