How to Show Live User Activity in Phoenix LiveView Using Presence and PubSub
HexShift

HexShift @hexshift

About: Asher Baum - Elixir/Phoenix enthusiast sharing advanced guides on Phoenix Framework, LiveView, WebSocket, Python and Tailwind. Helping devs build reactive, scalable apps with deep, practical insights.

Joined:
Apr 5, 2025

How to Show Live User Activity in Phoenix LiveView Using Presence and PubSub

Publish Date: Jun 13
5 1

Most web apps are solitary by default.

You open a tab, click a form, submit data—yet you never see who else is there.

That isn’t a web limitation; it’s a design limitation.

Phoenix LiveView breaks the mold.

With Phoenix Presence + PubSub, you can make any interface:

  • Social
  • Alive
  • Aware

What Presence Really Is

Presence is awareness — not analytics.

  • See who joins a chat room
  • Know who’s editing a document
  • Show “currently viewing” badges
  • Render typing indicators, collaborative cursors, and more

It surfaces real‑time context that builds trust.


How Presence Works (High‑Level)

  1. Client connects to a LiveView
  2. LiveView subscribes to a topic:
   Phoenix.PubSub.subscribe(MyApp.PubSub, "client:123")
Enter fullscreen mode Exit fullscreen mode
  1. Track the user:
   Phoenix.Presence.track(
     self(),           # LiveView PID
     "client:123",     # topic
     user.id,          # unique key
     %{name: user.name, avatar: user.avatar_url}
   )
Enter fullscreen mode Exit fullscreen mode
  1. Presence diff is broadcast automatically
  2. Every client renders updates immediately (no polling)

Example: Real‑Time CRM Editing

# In mount/3
topic = "client:#{client.id}"
Phoenix.PubSub.subscribe(MyApp.PubSub, topic)

Phoenix.Presence.track(
  self(), topic, user.id, %{name: user.name}
)

# In handle_info for presence diffs
def handle_info(%{event: "presence_diff"} = msg, socket) do
  users = MyPresence.list(socket.assigns.topic)
  {:noreply, assign(socket, users: users)}
end
Enter fullscreen mode Exit fullscreen mode

Now you can render:

<ul>
  <%= for {_id, %{metas: [meta]}} <- @users do %>
    <li><%= meta.name %> is here</li>
  <% end %>
</ul>
Enter fullscreen mode Exit fullscreen mode

No polling. No extra JavaScript. Pure LiveView.


Why Presence Scales

  • Diff‑based updates → minimal payloads
  • Tracker keeps memory low across nodes
  • Built for distributed clusters
  • Works with millions of presence events

Enhancing UX with Presence Metadata

Idea How to Track
Join time %{joined_at: DateTime.utc_now()}
Idle status (2 min) Client‑side hook updates idle: true
Custom avatar changes Update %{avatar_url: ...}

Your LiveView reacts in handle_info and re‑renders instantly.


Pattern: Store Presence in Assigns

def handle_info(%{event: "presence_diff"}, socket) do
  users = MyPresence.list(socket.assigns.topic)
  {:noreply, assign(socket, :users, users)}
end
Enter fullscreen mode Exit fullscreen mode

UI latency is near‑zero.

The client simply reacts to truth.


Design Philosophy

Presence isn’t just a feature.

“Make state visible. Reflect it fast. Let users feel the other people in the room.”

No extra APIs, no polling, no hacks—just:

  1. Topic namespace
  2. Track + diff
  3. Render the truth

Ready for More?

If you’re serious about Phoenix LiveView, download my PDF:

Phoenix LiveView: The Pro’s Guide to Scalable Interfaces and UI Patterns

  • Collaborative tools
  • Real‑time dashboards
  • Presence patterns
  • Production‑grade advice

Make your LiveView apps more powerful, usable, and real.

Comments 1 total

  • Administrator
    AdministratorJun 13, 2025

    If you've published on Dev.to, read this: Dev.to is distributing DEV Contributor rewards in recognition of your efforts on Dev.to. Visit the claim page here. instant distribution. – Dev.to Community Support

Add comment