LiveViewで認証付きのチャットアプリを構築する(リアルタイム更新作成)

はじめに

これまでの実装で、LiveViewを使って自分の発言を表示することができるようになりました。
ただし、その掲示板を見ている全員にもリアルタイムに状況を更新するには、もうひと段階が必要になります。
今回は、その部分を実装します。
本記事でリアルタイム更新が可能になります。

本テーマについて

本テーマは7記事構成になっています。
7記事それぞれで取り扱う内容は以下です。

  1. プロジェクト作成からログイン処理まで
  2. 認証機能の作成
  3. チャットボードのコンテキスト周り作成
  4. チャットボードの画面周り作成
  5. Phoenix.PubSubを使用したリアルタイム更新(本記事)
  6. Phoenix.Presenceを使用したアクティブユーザー表示
  7. 入力ステータスの表示

本テーマは以下の方針で記述します。

  • 機能実装は本テーマに沿った必要最小限のみ
  • UIもほぼそのまま
  • 機能へのアクセスはURLで直に指定を想定

完成版の画面イメージは以下のようになっています。

lv_chat

リアルタイム更新

現状では、投稿をしたら自分のブラウザはリアルタイムに更新がされるようになるものの、同じチャットボードを見ている他のユーザーには状態が反映されません。
状態が反映されるようにするためには、Phoenix.PubSubを利用します。

手順

行うことは以下の3点です。

  1. mount時にsubscribeを行う
  2. コメントが書き込まれたらbroadcast_fromを行う
  3. broadcast_fromで指定したメッセージのハンドラを追加

マウント時の処理追加

トピックIDを board:<<ボードID>> という形式にします。
例えばID=1のチャットボードは "board:1" となります。
そのトピックIDに対してマウント時に購読処理を行います。
ハイライト箇所が、今回追加したところです。

# /lib/lv_chat_web/views/board_live_view.ex
defp topicId(board_id) do  "board:#{board_id}"end
def mount(session = %{current_user: _user, board: board}, socket) do
  assigns =
    Map.merge(
      session,
      %{
        comments: board |> Meeting.list_comments,
        comment:  LvChat.Meeting.change_comment
      }
    )

  session.board.id  |> topicId  |> LvChatWeb.Endpoint.subscribe()
  {:ok, assign(socket, assigns)}
end

add_commentイベント時にブロードキャスト

LiveViewにてコメントが追加されたときに、broadcastを行う処理を追加します。
ハイライト箇所が、今回追加したところです。

# /lib/lv_chat_web/views/board_live_view.ex
def handle_event(
      "add_comment",
      %{"comment" => cmnt},
      socket = %{assigns: %{board: board, current_user: user}}
    ) do
  case Meeting.comment_to_board(user, board.id, cmnt) do
    {:ok, _comment} ->
      comments = board |> Meeting.list_comments

      LvChatWeb.Endpoint.broadcast_from(self(), board.id |> topicId, "broadcast_comment", %{        comments: comments      })
      {:noreply, assign(socket, comment: LvChat.Meeting.change_comment, comments: comments)}

    {:error, _changeset} ->
      {:noreply, socket}
  end
end

ブロードキャストイベントへの対応

broadcast_commentに対応します。
stateに全発言が含まれていますので、これを更新するだけです。

def handle_info(%{event: "broadcast_comment", payload: state}, socket) do
  {:noreply, assign(socket, state)}
end

これで、チャットとしての機能は完成しました。

終わりに

リアルタイムに情報が更新される部分の実装は、割と簡単に終わりました。

Phoenix.Presenceを使用したアクティブユーザー表示