Elixir Phoenix PubSub Tutorial
This page is in progress
Prerequisites
You should know how to launch a 1 page Live View app that lets the user submit data via a form. The form data prints to the page.
Disclaimer
To truly understand Live VIew pub sub, you need to have a strong understanding of Genserservers. This tutorial attempts to side step knowledge of GenServers so that you can use PubSub immediately (while learning about GenServers on your own time). As a result, some of the material in this writing is not completely technically correct. My intent is simplicity, brevity and utility for the reader - not 100 percent technical accuracy.
This tutorial explains the basics of building a Phoenix PubSub chat app.
The tutorial is divided into three parts, each part building on the completion of the previous one.
Project Description
- Part 1: We create a two route live view app with each route assigned its own page. The two routes are /send and /receive.
The page at /send contains an html form and form submission code. When data is submitted, the /receive page receives the data, and renders it to its page. The code we write for this exercise uses pubsub functions to connect the two LiveView pages.
When complete, you will have two browser tabs open, one opened to /send and one opened to /receive. The /send route sends data to /receive and you view the update in real time.
- Part 2: We convert the previous app into a single LiveView page that performs both actions on a single route. The end result is the creation of a single page chat application containing real time updates that you view across browser tabs.
- Part 3: We write code to update your single page app to store chat data in a database.
Part 1
Setup
To begin we will start with an new Phoenix instance. In your terminal create a new phoenix app by typing:
mix phx.new app
When the console prompts you to Fetch and install dependencies, choose yes.
When you see the following instructions configure your database, run mix ecto.create.
We are almost there! The following steps are missing: $ cd app Then configure your database in config/dev.exs and run: $ mix ecto.create Start your Phoenix app with: $ mix phx.server You can also run your app inside IEx (Interactive Elixir) as: $ iex -S mix phx.server
If you are new to Elixir and are having trouble with database setup, please fix those issues before proceeding with this tutorial.
When the app is created, open your web browser to localhost:4000. The app will launch. If it doesn't, fix the errors before moving forward.
Create Routes
Go to the router and update it as shown.
directory: app/app_web/router.ex
scope "/", AppWeb do pipe_through :browser live "/send", SendLive, :home live "/receive", ReceiveLive, :home get "/", PageController, :home end
Create the LiveView Pages
Create a new folder named live in this directory: App/lib/app_web. The end result looks like this: App/lib/app_web/live.
In the live directory, create two files and name them send_live.ex and receive_live.ex.
Edit Send Live.ex
Open the file send_live.ex in your text editor. Paste the following code into it.
defmodule AppWeb.SendLive do use AppWeb, :live_view def mount(_params, _session, socket) do {:ok, socket} end def handle_event("send", %{"text" => text}, socket) do IO.inspect text Phoenix.PubSub.broadcast(App.PubSub, "message", {:text_stuff, text}) {:noreply, socket} end defp topic do #Topic "message" end def render(assigns)do ~H""" <div> <h1>Send Message</h1> <form phx-submit="send"> <input type="text" name="text" /> <button type="submit">Send</button> </form> </div> """ end end
Open receive_live.ex and copy the following code to it.
defmodule AppWeb.ReceiveLive do use AppWeb, :live_view def mount(_params, _session, socket) do if connected?(socket) do Phoenix.PubSub.subscribe(App.PubSub, topic) end {:ok, assign(socket, message_item: "")} end def handle_info({:text_stuff, text}, socket) do {:noreply, assign(socket, message_item: text)} end defp topic do # Topic "message" end def render(assigns)do ~H""" <div> <h1>ChatLive</h1> <%= @message_item %> </div> """ end end
Before learning how this code works, first launch the server and open up two browser tabs. Set one to /send and the other to /receive
Submit data from /send and view the result on /receive
Play with the code and explore it. Ensure that it works.
Description
Before explaining the code, I would like to summarize the important parts applicable to all PubSub applications. Direct your attention to the three functions below. For now, treat these functions as the main characters in a PubPub program.
- Phoenix.PubSub.broadcast(App.PubSub, topic, {:text_stuff, text})
- Phoenix.PubSub.subscribe(App.PubSub, topic)
- handle_info({:text_stuff, text}, socket)
For brevity I refer to these as broadcast,subscribe and handle_info.
- broadcast
- subscribe
- handle_info
All PubSub applications are composed of these three functions.
A Useful Mental Model
Mental Models are helpful not because they are 100 percent accurate but because they are tangibly useful. The mental model I use is as follows:
If the first two arguments of broadcast and subscribe match each other....
- Phoenix.PubSub. broadcast (App.PubSub, topic, {:text_stuff, text})
- Phoenix.PubSub. subscribe (App.PubSub, topic)
Then...
handle_info is enabled with the ability to listen for and receive payload data from broadcast.
handle_info ({:text_stuff, text}, socket)
This may be confusing because you might assume handle_info would have to be given the same "topic" as the other two functions. This is not the case. As long as handle_info is in the same Live View module as subscribe, it automatically listens for the incoming matching broadcast payload data.
Explanation
broadcast
1. In SendLive the code Phoenix.PubSub.broadcast is used to broadcast message data. This method takes three arguments.
- The pubsub type
- The topic
- The payload (the data you want to send to all listeners)
The pubsub type is always going to be named your-app dot PubSub (such as App.PubSub) For our purposes you can treat this argument as boiler plate code.
Phoenix.PubSub.broadcast(App.PubSub)
The second argument, called the topic, is a string that represents a connection between the broadcast function and a subscribe function. The topic connects them.
Phoenix.PubSub.broadcast( App.PubSub, "some-topic-goes-here" )
The third argument is the payload. This can be any data you want to transmit between Live Views and can be any type of data.
Phoenix.PubSub.broadcast( App.PubSub, "some-topic-goes-here",%{data: "hello world})
subscribe
The first two arguments of subscribe mirror broadcast.
- Phoenix.PubSub. subscribe (App.PubSub, topic)
To use subscribe in a Module, you place it in the mount function.
handle_info
This function is used in GenServers. When used with a Live View it listens for payload data being sent via the broadcast method.
Part 2
Convert the App to a Single Live View Page
To convert the app to a single page you do these things:
- Copy and replace the mount function from ReceiveLive to SendLive.
- Copy the handle_info from ReceiveLive to SendLive
- Copy the HTML from ReceiveLive and embed it in the ~H of SendLive.
The result looks like this:
defmodule AppWeb.SendLive do use AppWeb, :live_view def mount(_params, _session, socket) do if connected?(socket) do Phoenix.PubSub.subscribe(App.PubSub, topic) end {:ok, assign(socket, message_item: "")} end def handle_info({:text_stuff, text}, socket) do {:noreply, assign(socket, message_item: text)} end def handle_event("send", %{"text" => text}, socket) do IO.inspect text Phoenix.PubSub.broadcast(App.PubSub, topic, {:text_stuff, text}) {:noreply, socket} end defp topic do #Topic "message" end def render(assigns)do ~H""" <div> <h1>Send Message</h1> <form phx-submit="send"> <input type="text" name="text" /> <button type="submit">Send</button> </form> <div> <h1>ChatLive</h1> <%= @message_item %> </div> </div> """ end end