現役フリーランスエンジニアが運営するテックメディア。日々の業務で得た知識を発信していきます!

  1. フロントエンド
  2. 568 view

ReactNative+Node.js+Pusherでリアルタイムチャットを構築するチュートリアル

最終更新日:2018/09/21

本記事では、ReactNative+Node.js+Pusher+react-native-gifted-chatの構成でリアルタイムチャットを構築する手順をチュートリアル形式で解説します。ある程度本格的なアプリを企画するとチャットでユーザ同士のコミュニケーションを促したくなるはず。
全手順を説明&ソースコードがGithub上にあるのでReactNativeやNode.jsの経験がそこまでない人でも読めるようになっています!!

作るもの

ソースコード

クライアントサイド
サーバサイド

Pusherアプリケーションを作る

Pusher


まず最初にPusherアカウントを作ります。Pusherはサーバとクライアントをリアルタイム通信してくれるサービスです。無料プランがあるのでちょっとしたコードを書くときにも気軽に使えます。
App ID、App Key、App SecretとClusterを取得します。
アカウントを持っていない方は作成しましょう!

アカウントを作成したらCreate New Appを選択します。

適当にアプリケーションの名前を決めて Create my appを押します。

ここにApp ID、App Key、App SecretとClusterが書いてあります。

以上でPusherの設定は終わりです。

サーバサイドを作る

Expressフレームワークを使ってNode.js上で動くサーバを実装します3つのRESTfulエンドポイントから構成されます。

[PUT]/users/ – ユーザがチャットルームに参加する
[DELETE]/users/ – ユーザがチャットルームから退出する
[POST]/messages – チャットルームにメッセージを投稿する

それでは、Node.jsアプリケーションを作って行きます。

mkdir chat-server
cd chat-server
npm init

色々聞かれますが、すべてEnterを押しましょう。

次に必要なパッケージをインストールします。

npm install --save express body-parser pusher

index.jsを以下のようにしましょう。

const express = require('express');
const bodyParser = require('body-parser');
const Pusher = require('pusher');

const pusherConfig = require('./pusher.json');
const pusherClient = new Pusher(pusherConfig);

const app = express();
app.use(bodyParser.json());

app.put('/users', function(req, res) {
  console.log('ユーザが参加しました:' + req.params.name);
  pusherClient.trigger('chat_channel', 'join', {
    name: req.body.name
  });
  res.sendStatus(204);
});

app.delete('/users', function(req, res) {
  console.log('ユーザが退出しました:' + req.params.name);
  pusherClient.trigger('chat_channel', 'part', {
    name: req.body.name
  });
  res.sendStatus(204);
});

app.post('/messages', function(req, res){
  console.log('ユーザ:' + req.body.name + 'メッセージ: ' + req.body.message);
  pusherClient.trigger('chat_channel', 'message', {
    name: req.body.name,
    message: req.body.message
  });
  res.sendStatus(204);
});

app.listen(4000, function() {
  console.log('App listening on port 4000');
});

ここでは先ほど紹介した3つのエンドポイントを定義しています。

[PUT]/users/ – ユーザがチャットルームに参加する
[DELETE]/users/ – ユーザがチャットルームから退出する
[POST]/messages – チャットルームにメッセージを投稿する

それぞれのエンドポイントが叩かれたときPusherClientがイベントをトリガーしてそれを購読するフロントエンド側にプッシュ通知が行くようになります。

またpusher.jsonを作って以下のようにしてください。

{
  "appId": "XXXXXX",
  "key": "XXXXXX",
  "secret": "XXXXXXX",
  "cluster": "XXX",
  "encrypted": true
}

ここには先ほどPusherアプリケーションを作成して得られた値を入れてください。

完了したら以下のコマンドでサーバを起動してみてください。

sudo node index.js

うまくいけばlocalhost:4000でサーバが起動します。コンソールにApp listening on port 4000と表示されるはずです。


例えばこんな感じでリクエストを送ると下の画像のようにPusherのイベントが発行されます。

以上でサーバサイドの実装は終わりです。

フロントエンドを作る

それではReact Nativeを使ってフロントエンドを作って行きましょう。creact-react-native-appを使って作ります。詳しい設定方法はReact Native入門〜Expoを使ってお手軽開発にまとめたので設定がお済みでない方はぜひ一度読んでみてください!

create-react-native-app chat-client
cd chat-client
npm start


と表示されるので好きなオプションを選んでください。iを押すとiOSシュミレータが起動してアプリが起動します。

ログイン画面の作成

まず名前を入力してチャットルームに入室できる画面を作ります。

import React from 'react';
import { StyleSheet, Text, TextInput, KeyboardAvoidingView } from 'react-native';

export default class Login extends React.Component {
  render() {
    return(
      <KeyboardAvoidingView style={styles.container} behavior="padding">
       <Text>名前を入力してください!</Text>
       <TextInput
        autoCapitalize="none"
        autoFocus
        keyboardType="default"
        maxLength={20}
        palaceholder="Username"
        returnKeyType="done"
        enablesReturnKeyAutomatically
        style={styles.username}
        onSubmitEditing={this.props.onSubmitName}
        />
      </KeyboardAvoidingView>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  username: {
    alignSelf: 'stretch',
    textAlign: 'center'
  }
})

チャット画面の作成

まずは必要なライブラリをインストールします。UIの画面を作るためにreact-native-gifted-chatというライブラリを使います。またPusher上で発行されたイベントを購読するためにpusher-jsというライブラリもインストールします。shortidはユニークな値を生成するために入れておきます。

npm install react-native-gifted-chat pusher-js shortid --save

ChatClient.jsを作成して以下のようにします。

import React from 'react';
import Pusher from 'pusher-js/react-native';
import { StyleSheet, Text, KeyboardAvoidingView } from 'react-native';

import pusherConfig from './pusher';

import { GiftedChat } from 'react-native-gifted-chat';

const shortid = require('shortid');

export default class ChatClient extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      messages: [],
    };
    this.pusher = new Pusher(pusherConfig.key, pusherConfig);

    this.chatChannel = this.pusher.subscribe('chat_channel');
    this.chatChannel.bind('pusher:subscription_succeeded', () => {
        this.chatChannel.bind('join', (data) => {
          this.handleJoin(data.name);
        });
        this.chatChannel.bind('part', (data) => {
          this.handlePart(data.name);
        });
        this.chatChannel.bind('message', (data) => {
          this.handleMessage(data.name, data.message);
        });
    });
    this.handleSendMessage = this.onSendMessage.bind(this);
    this.onSendMessage = this.onSendMessage.bind(this);
  }

  handleJoin(name) {
    if(name == this.props.name) {return;}
    const id = shortid.generate();
    this.setState({
      messages: GiftedChat.append(
        this.state.messages,
        {
          _id: id,
          user: {name:name},
          text: 'ただいま参加いたしました!'
        }
      )
    });
  }

  handleMessage(name, message) {
    if(name == this.props.name) {return;}
    const id = shortid.generate();
    this.setState({
      messages: GiftedChat.append(
        this.state.messages,
        {
          _id: id,
          user: {name:name},
          text: message
        }
      )
    });
  }

  componentDidMount() {
    const payload = {
      name : this.props.name
    };
    fetch(`${pusherConfig.restServer}/users`, {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(payload)
    });
  }

  componentWillUnmount() {
    const payload = {
      name: this.props.name
    };
    fetch(`${pusherConfig.restServer}/users`, {
      method: 'DELETE',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(payload)
    });
  }

  onSendMessage = (text) => {
    const payload = {
      name: this.props.name,
      message: text
    };
    fetch(`${pusherConfig.restServer}/messages`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(payload)
    });
  }
  onSend(messages) {
    this.setState(previousState => ({
      messages: GiftedChat.append(previousState.messages, messages),
    }));
    for(let message of messages) {
      this.onSendMessage(message.text);
    }

  }

  render() {
    const messages = this.state.messages;
    return (
      <GiftedChat
          messages={this.state.messages}
          onSend={messages => this.onSend(messages)}
          user={{
            _id: this.props.id,
            name: this.props.name,
          }}
      />
    )
  }
}

componentDidMountでPusherイベントの購読を開始し、Pusherで発行されるjoinpartmessageに対してコールバックを指定しているのがポイントです。

最後にApp.jsを以下のようにします。

import React from 'react';
import Login from './Login';
import ChatClient from './ChatClient';
const shortid = require('shortid');
export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmitName = this.onSubmitName.bind(this);
    this.state = {
      hasName: false
    }
  }
  onSubmitName(e) {
    const name = e.nativeEvent.text;
    this.setState({
      id: shortid.generate(),
      name,
      hasName: true
    });
  }
  render() {
    if(this.state.hasName) {
      return (
        <ChatClient id={this.state.id} name={this.state.name} />
      );
    } else {
      return (
        <Login onSubmitName={this.handleSubmitName}/>
      );
    }
  }
}

ログイン状態を判定し、コンポーネントを出し分けてる処理を実装しています。

ここまでできたら先ほどのNodeサーバを起動し、

npm start

してみてください。

Pusher上でイベントを発行してみたり、エンドポイントを叩いてみたりすると画像のようにリアルタイムに更新されるチャットができると思います。

最後までお付き合いいただきありがとうございました〜〜

The following two tabs change content below.
riri

riri

WINDII(ウィンディ)は、フリーランスエンジニアが運営するテックメディアです。 日々の業務で得た知見を、皆さんに役立つコンテンツにして発信していくので応援よろしくお願いします! また、Slackで無料コミュニティも運営しています。たくさんのエンジニアが参加していて、プログラミングの相談や雑談などをしている楽しいコミュニティなので、興味ある方はぜひお気軽にご参加ください。 Slackコミュニティはこちらから

フロントエンドの最近記事

  1. IOSアプリをAppStoreに公開する手順書(Ionic)

  2. IonicAcademyでIonic&Angularでのアプリ開発を学ぶ

  3. Ionic4+firebaseで認証をする方法【ログイン、ログアウト機能の実装】

  4. ハイブリッドアプリ開発チュートリアル【Ionic4+OnsenUI】

  5. Ionic4+Angular Materialの始め方【Ionic事前調査兵団】

関連記事

コメント

  1. この記事へのコメントはありません。

  1. この記事へのトラックバックはありません。

PAGE TOP