Getting Started with Kuzzle and React Native #
This section deals with Kuzzle V2 (+ Javascript SDK 7) and React Native. We will create documents in Kuzzle and subscribe to document notifications to develop a realtime chat.
Requirements #
- Node.js >= 12.0.0 (install here (opens new window))
- Running Kuzzle V2 Stack (instructions here)
- Expo CLI (install here (opens new window))
"Expo (opens new window) is a framework and a platform for universal React applications. It is a set of tools and services built around React Native and native platforms that help you develop, build, deploy, and quickly iterate on iOS, Android, and web apps from the same JavaScript/TypeScript codebase."
Note that this getting-started uses Expo for ease of use, the following React Native code is just as valid without Expo.
Prepare your environment #
Create your React Native app with Expo CLI.
expo init getting-started-kuzzle
You can use yarn to install dependencies if you want. Then choose a blank project, type 'Kuzzle' and type y.
Install Kuzzle's Javascript SDK:
cd getting-started-kuzzle
npm install kuzzle-sdk@7
Now, you can run your app and access it by different ways:
- To test it on your smartphone, you'll need to download the expo application and scan the displayed qrCode after running this command:
expo start
- To test it in your browser, you can run the following command:
expo start --web
Get the username #
First of all, we need to know the user's name. Let's start creating the following file screens/Login/Login.js and add some imports.
import React from "react";
import {
StyleSheet,
Text,
TextInput,
KeyboardAvoidingView
} from "react-native";
Then we must export our Login
class with the render function:
export default class Login extends React.Component {
render() {
return (
<KeyboardAvoidingView style={styles.container} behavior="padding">
<Text>Type your username:</Text>
<TextInput
autoCapitalize="none"
autoCorrect={false}
autoFocus
keyboardType="default"
maxLength={20}
placeholder="Username"
returnKeyType="done"
enablesReturnKeyAutomatically
style={styles.username}
onSubmitEditing={this.props.onHandleSubmitName}
/>
</KeyboardAvoidingView>
);
}
}
As you can see, this component has no state. It'll send the typed username
to his parent component by a onSubmitName
method passed as props.
To finish, we can add some style properties to let our login page looks prettier.
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center"
},
username: {
alignSelf: "stretch",
textAlign: "center"
}
});
This parent component of Login.js will be App.js so let's work on it.
You can delete all the code from the App.js file, we will rewrite it.
First, add the following imports:
import React from "react";
import Login from "./screens/Login/Login";
import ChatClient from "./screens/Chat/ChatClient";
Then we need to do three changes:
- First, we must export our App class:
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
hasName: false
};
}
- Then we need to create our
onSubmitName
function:
handleSubmitName(e) {
const name = e.nativeEvent.text;
this.setState({
name,
hasName: true
});
}
- Finally we will create our render function:
render() {
if (this.state.hasName) {
return <ChatClient name={this.state.name} />;
} else {
return <Login onHandleSubmitName={(e) => this.handleSubmitName(e)} />;
}
}
}
You can see that we will have to create a new component, ChatClient
.
That new component will contain the link to our Kuzzle backend so we need first declare our Kuzzle service.
Instantiating Kuzzle SDK and Adding logical part #
We have to connect the server so our client can interact with it.
To do this, we have to create a /services/kuzzle.js file to put our Kuzzle instance declaration:
import { Kuzzle, WebSocket } from 'kuzzle-sdk';
const options = {
offlineMode: 'auto'
};
export default new Kuzzle(new WebSocket('localhost'), options);
Note that if you are running your application on your smartphone, you must be connected on the same network and you'll need to change the localhost
in the previous snippet by your computer ip address.
We can now create a screens/Chat/ChatClient.js file to use our kuzzle service.
Like in our previous components, we'll first need to add our imports:
import React from "react";
import ChatView from "./ChatView";
import kuzzle from "../../services/kuzzle";
Then we will create our state that will contains our messages and call the function that will initialize our kuzzle by establish the connection to kuzzle and create, if they don't exist, the index and collection of our chat.
class ChatClient extends React.Component {
constructor(props) {
super(props);
this.state = {
messages: []
};
this.kuzzle = kuzzle;
this.handleSendMessage = this.handleSendMessage.bind(this);
}
async componentDidMount() {
await this.initConnection();
await this.fetchMessages();
await this.subscribeMessages();
}
async initConnection() {
// Etablish the connection
await kuzzle.connect();
// Check if 'chat' index exists
if (!(await kuzzle.index.exists("chat"))) {
// If not, create 'chat' index and 'messages' collection
await kuzzle.index.create("chat");
await kuzzle.collection.create("chat", "messages");
}
}
You can see that we are calling two functions at the end:
fetchMessages()
and subscribeMessages()
, let's write them.
Create the following function to fetch the messages:
async fetchMessages() {
// Call the search method of the document controller
const results = await this.kuzzle.document.search(
"chat", // Name of the index
"messages", // Name of the collection
{ sort: { "_kuzzle_info.createdAt": { order: "asc" } } }, // Query => Sort the messages by creation date
{ size: 100 } // Options => get a maximum of 100 messages
);
// Add messages to our array after formating them
await this.setState({
messages: results.hits.map((msg, msgIdx, arrayOfMsg) => {
let displayDate;
if (msgIdx === 0) {
// We always display the date for the first fetched message
displayDate = true;
} else {
// Check if the message is in the same day of the previous
displayDate = this.displayDate(
arrayOfMsg[msgIdx - 1]._source._kuzzle_info.createdAt,
msg._source._kuzzle_info.createdAt
);
}
return this.getMessage(msg, displayDate);
})
});
}
The function fetchMessage()
will search for the first hundred newest messages and store them in our state, before subscribing to changes in the messages
collection.
Then, create the subscribeMessages()
function.
It will call the Kuzzle's realtime controller to allow us to receive notifications on message creations:
async subscribeMessages() {
// Call the subscribe method of the realtime controller and receive the roomId
const roomId = await this.kuzzle.realtime.subscribe(
"chat", // Id of the index
"messages", // Id of the collection
{}, // Filter
// Callback to receive notifications
async notification => {
// Check if the notification interest us (only document creation)
if (notification.type !== "document") return;
if (notification.action !== "create") return;
const length = this.state.messages.length;
let displayDate;
if (length === 0) {
// If we haven't fetched some messages we must display the date for the first message we receive
displayDate = true;
} else {
// Check if the message is in the same day of the last message
displayDate = this.displayDate(
this.state.messages[length - 1].date,
notification.result._source._kuzzle_info.createdAt
);
}
// Add the new message to our array
await this.setState({
messages: [
...this.state.messages.slice(),
this.getMessage(notification.result, displayDate)
]
});
}
);
// Save the id of our subscription (we could need it to unsubscribe)
this.setState({ roomId: roomId });
}
You can see in the two function we just wrote a call to a getMessage()
function when we are adding a new message to the state. This function will format our Kuzzle's notification/response.
getMessage(msg, displayDate) {
const message = {
// The unique id of the document containing the message
id: msg._id,
// The text of the message
message: msg._source.message,
// The creation date
date: msg._source._kuzzle_info.createdAt,
// The author name
author: msg._source.author,
// Boolean to display or not the date
displayDate
};
// displayDate will be set to true only if the previous message is from
// another day in goal to display only one time the dates and only the
// hours on each messages
return message;
}
Note: The first part of this function is optional and can be replaced by a set of the display
variable to false.
If you decided to add the optional part, you'll need to implement the displayDate()
function:
displayDate(previousDate, currentDate) {
if (previousDate === null) {
// Message is the first of the array so we need to display the date
return true;
}
const d1 = new Date(previousDate).toDateString();
const d2 = new Date(currentDate).toDateString();
if (d1 !== d2) {
// Previous message and current has different dates so we need to display the date
return true;
}
// Previous message and current has same dates so we doesn't need to display the date
return false;
}
We will need a function to create a document in Kuzzle when an user send a message. Let's implement that function:
async handleSendMessage(message) {
await kuzzle.document.create(
"chat",
"messages",
// The document to be stored is passed in parameter
{
author: this.props.name,
message
}
);
}
This simple method will create a new message document in Kuzzle.
As you can see we don't push the new message in our array on message creation.
Indeed, we will receive notifications from Kuzzle each time we modify our message collection (even if it is a message creation on our part) that we will use to add the messages in our array.
Since this component is used to do the connection to Kuzzle, it will render another one that will display the messages screens/Chat/ChatView.js.
To finish, we just need to add the render()
function:
render() {
const messages = this.state.messages;
return (
<ChatView
messages={messages}
onHandleSendMessage={this.handleSendMessage}
name={this.props.name}
/>
);
}
}
export default ChatClient;
As you can see, we are passing as props the fetched messages, the client username and the handleSendMessage
function.
Display and Send messages #
In this part we'll work on another component, so we need to create the screens/Chat/ChatView.js file.
Then, just add the following imports:
import React from "react";
import {
StyleSheet,
TextInput,
View,
Text,
FlatList,
SafeAreaView,
KeyboardAvoidingView
} from "react-native";
Now we need to create our ChatView
class and create an handleSendMessage
function that will call the props parent function onHandleSendMessage
.
class ChatView extends React.Component {
constructor(props) {
super(props);
this.handleSendMessage = this.handleSendMessage.bind(this);
}
handleSendMessage(e) {
this.props.onHandleSendMessage(e.nativeEvent.text);
this.refs.input.clear();
}
There is no logic in this component but we will need a function to display the messages of our list: renderFlatListItem(item)
:
renderFlatListItem(item) {
return (
<View>
<View>
{item.displayDate && (
<Text style={styles.date}>
{new Date(item.date).toDateString()}
</Text>
)}
</View>
<View
testID={this.props.name === item.author ? "fromMe" : "fromOthers"}
style={
this.props.name === item.author ? styles.currentUser : styles.others
}
>
<View
style={{
flex: 1,
flexDirection: "row",
justifyContent: "space-between",
marginBottom: 5
}}
>
<Text style={styles.name}>{item.author}</Text>
<Text style={styles.date}>{this.getDate(item.date)}</Text>
</View>
<Text style={styles.message}>{item.message}</Text>
</View>
</View>
);
}
The first view part in the this method is used to display the date only once for each message sent at the same date. Now, let's work on the render function:
render() {
return (
<SafeAreaView style={{ flex: 1 }}>
<KeyboardAvoidingView style={styles.container} behavior="padding">
<FlatList
style={styles.list}
data={this.props.messages}
ref={ref => (this.flatList = ref)}
onContentSizeChange={() =>
this.flatList.scrollToEnd({ animated: true })
}
renderItem={item => this.renderFlatListItem(item.item)}
keyExtractor={item => item.id}
/>
In the previous snippet, you can see that we are using a Flatlist
to display the messages passed as props from the ChatClient
component and that we are calling our renderFlatListItem
function to render the messages.
Then just add the TextInput
part with a call to our handleSendMessage
function:
<TextInput
style={styles.inputMessage}
placeholder="Send message..."
onSubmitEditing={this.handleSendMessage}
keyboardType="default"
returnKeyType="done"
enablesReturnKeyAutomatically
blurOnSubmit={false}
ref="input"
/>
And don't forget to close the opened tags:
</KeyboardAvoidingView>
</SafeAreaView>
);
}
}
To finish, just add the style part to make it beautiful:
const offset = 24;
const styles = StyleSheet.create({
name: {
fontWeight: "bold"
},
date: {
textAlign: "center"
},
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "flex-start",
justifyContent: "flex-start"
},
list: {
marginTop: 30,
marginBottom: 30,
alignSelf: "stretch"
},
currentUser: {
backgroundColor: "#85EA41",
alignSelf: "flex-end",
margin: 5,
width: 200,
padding: 5,
borderRadius: 5
},
others: {
backgroundColor: "#1478FC",
alignSelf: "flex-start",
margin: 5,
width: 200,
padding: 5,
borderRadius: 5
},
inputMessage: {
alignSelf: "stretch",
borderColor: "#111111",
borderWidth: 1,
borderRadius: 15,
borderColor: "grey",
margin: 5,
paddingHorizontal: offset
}
});
export default ChatView;
You can now add new messages to Kuzzle and receive the creation notification to update your state and display the new messages.
Where do we go from here? #
Now that you're more familiar with Kuzzle, dive even deeper to learn how to leverage its full capabilities:
- discover what this SDK has to offer by browsing other sections of this documentation
- learn more about Kuzzle realtime engine
- follow our guide to learn how to manage users, and how to set up fine-grained access control
- lean how to use Kuzzle Admin Console (opens new window) to manage your users and data
- learn how to perform a basic authentication