DEV Community

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

Posted on

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

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.

Top comments (0)

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