Programming Phoenix LiveView:B4.0(page 248) Presence.track_user

I humbly believe there is a bug in the code in page 248:

 Presence.track_user(self(), product, user_token)

self() is going to give each user liveview pid. And it would be different.
I tried the code and when there is one user already viewing a product, the additional user visiting causing the code of Presence.update(pid,…) on presence.ex to do nothing, because the two pids are different. Actually if you log its return is {:error, :nopresence}

I even tried the downloadable code from the PragProg site with the same results.
There is no way, with the provided code, to see that two users are viewing the same product (ie. “Chess”)

I also found something relevant, and a solution (registering the pid) at this link : Global/Shared Presence {:error, :nopresence} - Nathan Willson

Please enlighten me.

Hello and thanks for submitting this question! You’re right that the self() returns the PID of the current live view. What we’re doing with this call to Presence.track_user(self(), product, token) is registering the current live view’s PID so that it can be tracked by Presence. For that PID, we are storing the name of the game being viewed, and the metadata describing the user. The ability to display all the users viewing a given game comes from the code that we use to retrieve Presence data. With this code:

# lib/pento_web/presence.ex
def list_products_and_users do
    Presence.list(@user_activity_topic)
    |> Enum.map(&extract_product_with_users/1)
  end

  defp extract_product_with_users({product_name, %{metas: metas}}) do
    {product_name, users_from_metas_list(metas)}
  end

  defp users_from_metas_list(metas_list) do
    Enum.map(metas_list, &users_from_meta_map/1)
    |> List.flatten()
    |> Enum.uniq()
  end

  def users_from_meta_map(meta_map) do
    get_in(meta_map, [:users])
  end

We fetch all the Presence data under the topic “user_activity”, then we organize it into a list of tuples in which the first element of the tuple is the name of the game, and the second element is a list of users who we stored under a given PID, with that game name. The call to Presence.list_products_and_users returns something that looks like this:

[{"Chess", [%{email: "bob@email.com"}, %{email: "terry@email.com"}]}]

I pulled down the latest code and was able to verify that it successfully displays the full list of users viewing a given game:

I think this code may have changed slightly in the latest Beta release though, so I’d say make sure that you’re looking at the code from the latest Beta release of the book and then give it another shot. Let me know if that doesn’t work!

Thanks Sophie!

I tried again and downloaded the source zip for the book page but I couldn’t find the functions you shared.
Maybe you pulled from a private GH repo.

Thanks anyway, I’ll try to code it myself.

Hmm its possible the new code actually hasn’t been released yet. Let me confirm that and get back to you. In the meantime, here is the correct version of the code you can use:

# lib/pento_web/presence.ex
defmodule PentoWeb.Presence do
  use Phoenix.Presence,
  otp_app: :pento,
  pubsub_server: Pento.PubSub

  alias PentoWeb.Presence
  alias Pento.Accounts
  @user_activity_topic "user_activity"

  def track_user(pid, product, token) do
    user = Accounts.get_user_by_session_token(token)
    Presence.track(
      pid,
      @user_activity_topic,
      product.name,
      %{users: [%{email: user.email}]})
  end

  def list_products_and_users do
    Presence.list(@user_activity_topic)
    |> Enum.map(&extract_product_with_users/1)
  end

  defp extract_product_with_users({product_name, %{metas: metas}}) do
    {product_name, users_from_metas_list(metas)}
  end

  defp users_from_metas_list(metas_list) do
    Enum.map(metas_list, &users_from_meta_map/1)
    |> List.flatten()
    |> Enum.uniq()
  end

  def users_from_meta_map(meta_map) do
    get_in(meta_map, [:users])
  end
end

Thank you!

BTW, I’m enjoying the book! :grinning:

1 Like