Skip to content

Instantly share code, notes, and snippets.

@shamshirz
Created June 4, 2023 16:03
Show Gist options
  • Save shamshirz/5fcf82942e98be38531064b9bccdd3e0 to your computer and use it in GitHub Desktop.
Save shamshirz/5fcf82942e98be38531064b9bccdd3e0 to your computer and use it in GitHub Desktop.
Liveview PubSub Example
defmodule App.Application do
@impl true
def start(_type, _args) do
children = [
{App.ScanTracker, []} # <- Add this line
]
opts = [strategy: :one_for_one, name: App.Supervisor]
Supervisor.start_link(children, opts)
end
end
defmodule AppWeb.Router do
scope "/", AppWeb do
pipe_through :browser
live "/scan", ScanStatus # <- Add this line (and scope if you don't have one already)
end
end
defmodule AppWeb.ScanStatus do
@moduledoc """
View only page that displays the live status of scan events.
All events are automatically pushed to any client on this page
No `handle_event/3` because the client pushes no events to the server
"""
use AppWeb, :live_view
def mount(_params, _session, socket) do
App.ScanTracker.subscribe()
{:ok, assign(socket, :scan_data, App.ScanTracker.get())}
end
# This handles messages from other Elixir Processes
def handle_info({:update, scan_data}, socket) do
{:noreply, assign(socket, :scan_data, scan_data)}
end
attr :scan_data, :map, required: true
def render(assigns) do
~H"""
<h1>Scan Status</h1>
<ul>
<%= for {scan_id, [event | _older_events]} <- @scan_data do %>
<li>
<%= scan_id %> : <%= event %>
</li>
<% end %>
</ul>
"""
end
end
defmodule App.ScanTracker do
@moduledoc """
An Agent that stores the current state of any scan events sent to it. Ephemeral, not in DB
Additionally, it offers a PubSub channel to subscribed to state changes
Any process on the server can "push" a scan event via this module.
The Agent process is updated with the new state, and that is broadcast to any liveview's subscribed to the channel.
## Thoughts - ETS & Genserver vs. Agent? Why and When?
"""
use Agent
alias Phoenix.PubSub
@channel "scan_tracker"
@doc "Starts the Tracker Process"
def start_link(_opts), do: Agent.start_link(fn -> %{} end, name: __MODULE__)
@doc "Gets all scan data"
def get, do: Agent.get(__MODULE__, & &1)
@doc "Put the `event` for the given `scan_id` at the head of it's event list. Broadcast that an update was made."
@spec put_scan_event(scan_id :: String.t, event :: any()) :: :ok
def put_scan_event(scan_id, event) do
Agent.update(__MODULE__, &Map.update(&1, scan_id, [event], fn existing_events -> [event | existing_events] end))
PubSub.broadcast(Blog.PubSub, @channel, {:update, get()})
end
def subscribe, do: PubSub.subscribe(Blog.PubSub, @channel)
end
@shamshirz
Copy link
Author

shamshirz commented Jun 4, 2023

Diagram of the relationship between events and modules.

Any process -> Tracker -> Liveviews

LiveViewPubSubDiagram

Demo

LiveViewPubSubDemoGiphy

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment