LiveViewで認証付きのチャットアプリを構築する(アクティブユーザー表示)

はじめに

前回までで、基本的なチャット機能は完成しました。
その他、一般的なチャットの機能として以下のようなものがあります。

  • そのボードを見ているユーザー
  • ユーザーのタイピング状況

これから2回に分けて上記機能を実装します。
今回はボードを見ているユーザーの表示を行います。

本テーマについて

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

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

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

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

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

lv_chat

アクティブユーザーの表示

準備

アクティブユーザーとは、チャットボードに参加しているユーザーの事です。
これを表示する機能を追加します。
Phoenix.Presence を使用するため、まずこれを追加します。
追加するには、まずmixコマンドを使用します。

$ mix phx.gen.presence

コマンドの出力メッセージで案内されたように、application.exLvChatWeb.Presence を追加します。

# /lib/lv_chat/application.ex
children = [
  # Start the Ecto repository
  LvChat.Repo,
  # Start the endpoint when the application starts
  LvChatWeb.Endpoint,
  # Starts a worker by calling: LvChat.Worker.start_link(arg)
  # {LvChat.Worker, arg},
  LvChatWeb.Presence]

これで準備は整いました。

ヘルパー関数の実装

Presence機能を使いやすくするために、Presenceのヘルパー関数を定義します。

関数役割り
track_presencePresence.trackへの転送関数
list_presences指定したトピックのリストを取り出しやすい形に変換して取得
extract_metadatametasからデータを取り出す

実装は以下のようになります。

# /lib/lv_chat_web/channels/presence.ex
alias LvChatWeb.Presence

def track_presence(pid, topic, key, payload) do
  Presence.track(pid, topic, key, payload)
end

def list_presences(topic) do
  topic
  |> Presence.list
  |> Enum.map(fn {_user_id, data} ->
    data
    |> extract_metadata
  end)
end

defp extract_metadata(data) do
  data
  |> Map.get(:metas)
  |> List.first
end

extract_metadata についてですが、もとのデータは以下のような形で取得されます。

%{metas: [%{id: 1, name: "freddie", phx_ref: "OoxOA/GE9hY="}]}

変換後は以下のようになります。

%{id: 1, name: "freddie", phx_ref: "OoxOA/GE9hY="}

複数のユーザーがそのチャットボードにアクセスしているときは、リストとして取得されます。

[
  %{id: 1, name: "freddie", phx_ref: "OoxOA/GE9hY="},
  %{id: 2, name: "brian", phx_ref: "aUeSKX6opX0="}
]

mount時の処理追加

mount時に以下の事を行います。

  • 自分を登録して追跡開始
  • 自分を含め現在のアクティブユーザーを取得

ハイライト箇所が今回追加したところです。

# /lib/lv_chat_web/views/board_live_view.ex
def mount(session = %{current_user: user, board: board}, socket) do
  Presence.track_presence(    self(),    board.id |> topicId,    user.id,    %{      name: user.name,      id: user.id,    }  )
  assigns =
    Map.merge(
      session,
      %{
        comments: board |> Meeting.list_comments,
        comment:  LvChat.Meeting.change_comment,
        users:          session.board.id          |> topicId          |> Presence.list_presences()      }
    )

  session.board.id
  |> topicId
  |> LvChatWeb.Endpoint.subscribe

  {:ok, assign(socket, assigns)}
end

変化通知イベントへの対応

追跡を開始すると、追跡情報の変化が生じる度に presence_diff イベントが送られてきます。
そのイベントに対応し、現在の追跡情報をsocketに指定します。
今回は、:users に追跡されている全ユーザーを指定しています。

def handle_info(%{event: "presence_diff"}, socket = %{assigns: %{board: board}}) do
  {:noreply, assign(socket, users: board.id |> topicId |> Presence.list_presences)}
end

アクティブユーザーの表示

アクティブユーザーを表示するコードは以下になります。
ハイライトされている箇所でユーザーを表示しています。
データの区別のために、 id ではフレームワーク側で付与している phx_ref を使用します。

# /lib/lv_chat_web/templates/board/show.html.leex
<h3>Users</h3>
<ul id="user-container">
  <%= for user <- @users do %>
    <li id="<%= user.phx_ref %>"><%= user.name %></li>  <% end %>
</ul>

これでアクティブユーザーの表示ができるようになりました。

終わりに

今回は、アクティブユーザーの表示を行いました。
Phoenixでは、リアルタイムステータスの更新はPresenceを使って実現します。
リアルタイムステータスとしては、今回のアクティブユーザーの他にタイピング状況があります。
次回では、その機能を実装します。

入力ステータスの表示