Psst. It would be super cool if you could try the new Phoenix Boilerplate!

Try now

Tutorial

Phoenix Presence with Phoenix LiveView

liveviewpresence

A lot of apps have some sort of notification on if users are online or not. Phoenix makes it easy to build that with the built in Phoenix Presence. In this tutorial, I will combine the presence feature with Phoenix LiveView. The realtime nature of LiveView makes it easy to display the current online users.

STEP 1 - Add a Presence Module

First I need to define a presence module and make sure its started in the application.

# lib/tutorial/presence.ex
defmodule Tutorial.Presence do
  use Phoenix.Presence, otp_app: :tutorial, pubsub_server: Tutorial.PubSub
end

Add it in the start function:

# lib/tutorial/application.ex
def start(_type, _args) do
  children = [
    ...other processes
    # Start Presence process
    PhoenixFeatures.Presence,
  ]
end

STEP 2 - Add Presence to LiveView

The idea is that when a visitor visits a page, I need to add that visit in a specific channel. In this case, I have a route that points to a specific LiveView so I can add the logic in the mount function.

I have already setup so the current_user is assigned in the user session in a plug. That means, I can get it as session["current_user"]. In this tutorial, I use the user id as the unique identifier in the presence channel.

So, in the LiveView that is responsible for showing the online users, I need to add:

# lib/tutorial_web/live/page_live.ex
defmodule TutorialWeb.PageLive do
  use TutorialWeb, :live_view

  alias Tutorial.Presence
  alias Tutorial.PubSub

  @presence "tutorial:presence"

  @impl true
  def mount(_params, session, socket) do
    if connected?(socket) do
      user = session["current_user"]

      {:ok, _} = Presence.track(self(), @presence, user[:id], %{
        name: user[:name],
        joined_at: :os.system_time(:seconds)
      })

      Phoenix.PubSub.subscribe(PubSub, @presence)
    end

    {
      :ok,
      socket
      |> assign(:current_user, session["current_user"])
      |> assign(:users, %{})
      |> handle_joins(Presence.list(@presence))
    }
  end
    
    @impl true
  def handle_info(%Phoenix.Socket.Broadcast{event: "presence_diff", payload: diff}, socket) do
    {
      :noreply,
      socket
      |> handle_leaves(diff.leaves)
      |> handle_joins(diff.joins)
    }
  end

  defp handle_joins(socket, joins) do
    Enum.reduce(joins, socket, fn {user, %{metas: [meta| _]}}, socket ->
      assign(socket, :users, Map.put(socket.assigns.users, user, meta))
    end)
  end

  defp handle_leaves(socket, leaves) do
    Enum.reduce(leaves, socket, fn {user, _}, socket ->
      assign(socket, :users, Map.delete(socket.assigns.users, user))
    end)
  end
end

Note that I use phoenix pubsub to notify all other PageLive-processes that are spawned for other page visitors.

I want to display the online users in a LiveView component called OnlineUsers. I mount the component in the template like:

# lib/tutorial_web/live/page_live.html.leex
<%= live_component @socket, TutorialWeb.Components.OnlineUsers, current_user: @current_user, users: @users %>

Note that I assign both current_user and all users.

In the component, I don’t need to setup anything specific. Just enough to render the template.

# lib/tutorial_web/live/components/online_users.ex
defmodule TutorialWeb.Components.OnlineUsers do
  use TutorialWeb, :live_component

  def update(assigns, socket) do
    {:ok,
      socket
      |> assign(assigns)
    }
  end
end

And lastly, in the template, rendering the online users are pretty straight forward.

<div>
  <h2>Users online:</h2>
  <%= for {user_id, user} <- @users do %>
    <%= if user_id == @current_user[:id] do %>
      <span class="text-xs bg-green-200 rounded px-2 py-1"><%= user[:name] %> (me)</span>
    <% else %>
      <span class="text-xs bg-blue-200 rounded px-2 py-1"><%= user[:name] %></span>
    <% end %>
  <% end %>
</div>

What are you working on?

If you want, you can send me a link to your Phoenix or Phoenix LiveView project so. Lets connect on Twitter or Linkedin.

- Andreas Eriksson, web developer since 2005

Related Tutorials

Published 02 Sep

Table sorting with Ecto and LiveView

ectoliveviewsorting

A very common or even mandatory feature in e-commerce stores is the ability to sort a list of products by attributes. This is easy enough and a good..

Published 23 Sep

Setup Stripe with Phoenix LiveView

stripepaymentsliveviewforms

In this tutorial, I will go through how I setup Stripe payments with Phoenix and LiveView to make your app prepared for accepting payments. The tuto..