Send notifications to all page in phoenix LiveView

Hi, how can I send a broadcast or a message without subscribing in any module, and I can be able to receive this message in all LiveView modules.
To put it simply, I want to say I need to create a notification center and my all LV module are subscribed and receive it’s messages.

I imagine something like this in my mind that every section of my site after does a job send it their message as a notification.
Screenshot 2020-03-03 at 3.41.33 am

Central and comprehensive notification

it should be noted Im forced not to use something can save my notifications in disk or db.

Thanks

1 Like

Would a separate notification liveview embedded in your app.html.eex work? That could subscribe to the source of your notifications.

1 Like

You want broadcasting without subscription? What does this mean? How would this work?

Can you elaborate on this requirement?

1 Like

Hi my friend, would you mind explaining me more please? how could embed this in my app.html.eex?

Sorry my friend, I’m new in this subject. I just need to send a broadcast and get it in every page, how can I do this?

I mean if my user is here in every page of my site!! can see my message in his browser. now my user just can see my message when he is in the page concerned which has subscribe to same module and includes the samehandle_info

for example: I create a post in admin, I have 3 users are in these sections of my site. (1. shop, 2. blog, 3. support). I need all of them see my message that is a new post has been created in Same time!!

So get a single liveview working to show notifications etc (i.e. subscribe to the source of the notifications and update the socket assigns when a notification is received).

Then display that liveview on every page by putting something like

<%= live_render(@conn, MyWebApp.NotificationViewerLive) %>

in your app.html.leex

To help you with how to subscribe to the source of notifications etc, we will need more information, but you will almost certainly end up using PubSub somewhere - you can read up on that here: https://hexdocs.pm/phoenix/1.1.3/Phoenix.PubSub.html

1 Like

is it browser notifications? then this one shows you how: https://curiosum.dev/blog/elixir-phoenix-liveview-messenger-part-4

3 Likes

I tested it, it works but but there is an error that I couldn’t fix.

if your client are in a page and don’t navigate to other page it is fine but if one of your clients navigate to other pages, it will be broken.

I created a video to explain, please see top right Notif box. I did 2 action { delete and create}

if you saw the video, there are problems, like closing box or doesnt show notification message

my code:

notification LV

defmodule ErfanWeb.Live.Notification do
   use Phoenix.LiveView
   alias Erfan.Notif
   def mount(_params, _session, socket) do
      if connected?(socket) do
         Erfan.Notif.subscribe()
      end
      {:ok, assign(socket, notif_status: false)}
   end

   def render(assigns) do
      ~L"""
      <%= if @notif_status do %>
         <section class="container rtl">
            <div class="toast fade show notcoustom rtl" role="alert" aria-live="assertive" aria-atomic="true">
               <div class="toast-header">
               <svg class="bd-placeholder-img rounded  " width="20" height="20" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice" focusable="false" role="img"><rect fill="#007aff" width="100%" height="100%"></rect></svg>
               <strong class="mr-2">پیام فوری</strong>
               <small class="mr-auto text-muted text-left"> همین الان </small>
               <button phx-click="close-notif" type="button" class="ml-2 mb-1 close mr-3" data-dismiss="toast" aria-label="Close">
                  <span aria-hidden="true">×</span>
               </button>
               </div>
               <div class="toast-body">
               <%= @msg %>
               </div>
            </div>
         </section>
      <% end %>
      - " "
   end

   def handle_event("close-notif", _value, socket) do
      IO.inspect "close noooooooooootif"
      {:noreply, assign(socket, notif_status: false)}
   end

   def handle_info({Notif, event, msg, status}, socket) do
      IO.inspect "noooooooooootif"
      {:noreply, assign(socket, notif_status: true, event: event, msg: msg, status: status)}
   end
 end

and it’s module:

defmodule Erfan.Notif do
   @topic inspect(__MODULE__)

   def subscribe do
      Phoenix.PubSub.subscribe(Erfan.PubSub, @topic)
   end

   def notify_subscribers(status, msg, event) do
      Phoenix.PubSub.broadcast(Erfan.PubSub, @topic, {__MODULE__, event, msg, status})
      {event, msg, status}
   end
end

one of the file I called it

defmodule ErfanWeb.Live.Categories do
   use Phoenix.LiveView
   alias Erfan.Admin.CategoryQuery
def mount(_params, _session, socket) do
      if connected?(socket), do: CategoryQuery.subscribe()
      {:ok, fetch(socket)}
end

def handle_event("delete_category", value, socket) do
      Erfan.Notif.notify_subscribers(:ok, "Category has been deleted", [:category, :delete])
      CategoryQuery.delete_category(value["category-id"])
      {:noreply, socket}
end

and the other where I called with navigate:

   def handle_event("save", %{"category_schema" => category_info}, socket) do
      case CategoryQuery.create_category(category_info) do
         {:ok, resutl} ->

            Erfan.Notif.notify_subscribers(:ok, "Category has been created", [:category, :new])

            {:noreply,
               socket
               # |> redirect(to: Routes.live_path(ErfanWeb.Endpoint, Categories))
               |> push_redirect(to: Routes.live_path(socket, Categories))
            }
         {:error, _, %Ecto.Changeset{} = changeset} ->
           {:noreply, assign(socket, changeset: changeset)}
       end
   end

in my app.html

<%= live_render(@conn, ErfanWeb.Live.Notification) %>

full project:

Thanks

I’ll take a look first thing tomorrow Western Australian time…

1 Like

I took a small look at the source of my page:

the first data-phx-session is my notification and the second is my main content. the second one allows is changed. but the first one after changing page it isn’t changed.

maybe I should change it to something!?

<%= live_render(@conn, ErfanWeb.Live.Notification) %>

I moved top code under Leex files and change @conn to @socket

<%= live_render(@socket, ErfanWeb.Live.Notification, id: :hero) %>

it had an error than forced me to put id in my live_render. and now It isn’t locked, every page it refresh’s data-phx-session and cleans last notification.

but there is still a problem, we have no @socket in app.html and I can’t put it just once for now I put that line in all pages I have

I took a look. I think there might be a liveview bug when a liveview is embedded within the layout (i.e. live_render in app.html.leex) - a live_patch causes the event handler to come detached. If you are able to create a minimal case to demonstrate that we can submit a bug report.

When the live_render is added to each of your liveviews it seems to work ok. The notification gets cleared when you move from one page to another - there’s no avoiding that unless you have some kind of store of notifications and their statuses kept separately from the liveviews - it really depends on what user experience you want. Similarly, you won’t get a “category added” notification on the browser tab you added the category from - the notification fires before the liveview is created to “catch” it and display it. Again, you would need some kind of temporary store if you want your users to see this.

I made a couple of minor changes to your notification liveview to allow display of multiple notifications. The list of notifications and logic for closing them could be moved to a separate process to provide the store mentioned above. Let us know your expected flow and we’ll have a go at making it work. I actually need something similar on a project I’m working on now, so the timing is good!

defmodule ErfanWeb.Live.Notification do
  use Phoenix.LiveView
  alias Erfan.Notif

  def mount(_params, _session, socket) do
    # if connected?(socket) do
    #    Erfan.Notif.subscribe()
    # end

    Erfan.Notif.subscribe()
    {:ok, assign(socket, notifs: [], last_id: 0)}
  end

  def render(assigns) do
    ~L"""
       <section class="container rtl" style="position: absolute; top: 30px; right: 0;">
       <%= for notif <- @notifs do %>
         <div class="toast fade show  rtl" role="alert" aria-live="assertive" aria-atomic="true">
             <div class="toast-header">
             <svg class="bd-placeholder-img rounded  " width="20" height="20" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice" focusable="false" role="img"><rect fill="#007aff" width="100%" height="100%"></rect></svg>
             <strong class="mr-2">پیام فوری</strong>
             <small class="mr-auto text-muted text-left"> همین الان </small>
             <button phx-value-id="<%= notif.id %>" phx-click="close-notif" type="button" class="ml-2 mb-1 close mr-3" data-dismiss="toast" aria-label="Close">
                <span aria-hidden="true">×</span>
             </button>
             </div>
             <div class="toast-body">
             <%= notif.msg %>
             </div>
          </div>
        <% end %>
      </section>
    """
  end

  def handle_event("close-notif", %{"id" => raw_id}, socket) do
    {id, ""} = Integer.parse(raw_id)
    notifs = Enum.reject(socket.assigns.notifs, &(&1.id == id))
    IO.inspect(notifs, label: "close noooootif")
    {:noreply, assign(socket, notifs: notifs)}
  end

  def handle_info({Notif, event, msg, status}, socket) do
    id = socket.assigns.last_id + 1
    new_notif = %{id: id, msg: msg, event: event, status: status}
    notifs = [new_notif | socket.assigns.notifs]
    IO.inspect(notifs, label: "new noooootif")
    {:noreply, assign(socket, notifs: notifs, last_id: id)}
  end
end

1 Like

Hi my friend, I don’t think to submit a bug because my English is not good :slight_smile: I’m not native. some issue I did but I couldn’t understand them what I meant. if you could it would be great.

Thanks for your suggestion, I submitted many commit today and Tomorrow( Iran Time) I will replace your changes on my code and commit again but before it I try to create easy way and do less code more than now.

but for now with my way I said!

it works, but we should repeat this code in every page

Thank you.

I have just reproduced it on the live_view_samples project so I’ll submit a bug… hopefully you will then be able to remove all the repeats.

2 Likes

ok - bug logged here:

4 Likes

yeh, you are right, but I’m creating a video to teach this LiveView in Persian language, and next lesson is genserver and redis, on that time I will store this, thank you for your suggestions.

I refactored my code and I used your code. this project is a small ordering for cafes.

3 Likes

he just closed without any comment :frowning:

I think it will be fixed in new version .


:shushing_face: :thinking:

This happens on Github if he pushes a commit that says “Fixes #issue-number”.


That’s what that ^ means.

4 Likes

Yes - and I have confirmed that it’s fixed in current github master against the test case. If you update your dependencies to point to the current commit you should be able to remove all the live_render calls from each liveview and have a single call in app.html.leex.

To do this:

  1. Change the liveview entry in mix.exs to {:phoenix_live_view, "~> 0.8.0", github: "phoenixframework/phoenix_live_view"}
  2. Unlock the liveview dependency - command line is mix deps.unlock phoenix_live_view
  3. Update the liveview dependency - mix deps.update phoenix_live_view

Let us know how you go with the genserver version.

2 Likes