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

"Expo 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."

Prepare your environment

Create your React Native app with Expo CLI.

Copied to clipboard!
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:

Copied to clipboard!
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:
Copied to clipboard!
expo start
  • To test it in your browser, you can run the following command:
Copied to clipboard!
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.

Copied to clipboard!
import React from "react";
import {
  StyleSheet,
  Text,
  TextInput,
  KeyboardAvoidingView
} from "react-native";

Then we must export our Login class with the render function:

Copied to clipboard!
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.

Copied to clipboard!
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:

Copied to clipboard!
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: <<< ./snippets/App.js.snippet:2[js]
  • Then we need to create our onSubmitName function: <<< ./snippets/App.js.snippet:3[js]
  • Finally we will create our render function: <<< ./snippets/App.js.snippet:4[js]

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:

Copied to clipboard!
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:

Copied to clipboard!
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.

Copied to clipboard!
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:

Copied to clipboard!
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:

Copied to clipboard!
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.

Copied to clipboard!
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:

Copied to clipboard!
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:

Copied to clipboard!
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:

Copied to clipboard!
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:

Copied to clipboard!
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.

Copied to clipboard!
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):

Copied to clipboard!
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:

Copied to clipboard!
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:

Copied to clipboard!
<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:

Copied to clipboard!
        </KeyboardAvoidingView>
      </SafeAreaView>
    );
  }
}

To finish, just add the style part to make it beautiful:

Copied to clipboard!
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: