Integrating Phoenix LiveView

Phoenix LiveView came out in a preview form at the end of last week, so for this week's SmartLogic TV live stream I wanted to integrate it into an existing project. Before the start of the stream I hadn't looked at any of the examples or docs on how to set it up; I had only seen Chris McCord's keynotes showing it off at ElixirConf and Lonestar Elixir.

One note before beginning on your own LiveView journey: it is still in development and you shouldn't base your whole app on it just yet.

Starting Point

For some context, I have a side project called Grapevine that lets you play old-school telnet-based multiplayer text games called MUDs. You can play these via a web client that connects via Phoenix channels back to an internal telnet client process which is actually on a separate BEAM node, to further complicate matters.

I have a simple admin dashboard that displays active web client connections, but it is static and does not auto update as they are opened or closed. Sounds like the perfect thing for a live view extension!

Integrating LiveView

To integrate LiveView I followed the README, which sets up the application for rendering live views.

In my EEx template I replaced the snippet that I wanted to turn "live" with this:

<%= live_render(@conn, OpenWebClientView, session: [clients: @clients]) %>

I mostly copied the full html into a new LiveView view.

defmodule OpenWebViewClient do
  use Web, :live

  alias Telnet.Presence

  def mount(_session, socket) do
    Web.Endpoint.subscribe("telnet:presence")
    socket = assign(socket, :clients, Presence.online_clients())
    {:ok, socket}
  end

  def render(assigns) do
    ~L[
    <h4>Open Web Clients</h4>
    ...
    ]
  end
end

The view also subscribes to an internal-only Phoenix Channel named telnet:presence that the web clients will be pushing notices over.

Receiving Messages

The last piece that makes this work is the handle_info function that receives the broadcasts:

def handle_info(broadcast = %{topic: "telnet:presence"}, socket) do
  case broadcast.event do
    "client/online" ->
      client_online(socket, broadcast.payload)

    "client/offline" ->
      client_offline(socket, broadcast.payload)

    "client/update" ->
      client_update(socket, broadcast.payload)
  end
end

def client_online(socket, client) do
  socket = assign(socket, :clients, [client | socket.assigns.clients])
  {:noreply, socket}
end

The magic happens anytime assigns/3 changes socket.assigns. The live view updates with the new values and re-renders automatically. Dynamic pages with no custom javascript.

See the other client functions on GitHub.

Conclusion

I was very impressed with how easy Phoenix LiveView is to set up. It was also fun to see how even when the return values for handle_info were off, I still got a correct display because the backing process crashed and simply restarted to a good state.

See the full code on GitHub:

Watch me code this whole example "live" on YouTube: