DEV Community

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

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

IdeaHow to Track
Join time%{joined_at: DateTime.utc_now()}
Idle status (2 min)Client‑side hook updates idle: true
Custom avatar changesUpdate %{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 dasards
  • Presence patterns
  • Production‑grade advice

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

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.