Hello Gustavo
The main reason for implementing widgets with a LiveView instead of the LiveComponent, was ( at least to my knowledge ) that is much easire handling PubSub messages, without having a central broker in the main LiveView that dispatches to the LiveComponents…
Since these is an IoT dashboard with a lot of live updates we though this approach would simplify the overall dispatching.
Having said this here is the code of an example widget ( broken down to the essential part ), whith also the trick to make everything work
@impl true
def mount(_params, session, socket) do
....
{:ok,
socket
...
|> assign(:ui_data_backup, %{ok?: false, loading: true, result: nil})
|> assign(:ui_data, AsyncResult.loading())
|> start_async(:load_ui_data, fn -> select_action_for_map_async(data_instance, user.timezone, widget, tenant_id) end)
…
}
end
def render(assigns) do
....
<div :if={@ui_data_backup.loading} class="h-full">
<div class="flex justify-center items-center w-full h-full">
<div class="text-center">
<h1 class="text-4xl font-bold text-black">
<%= gettext("Loading Data") %>
</h1>
<p></p>
</div>
</div>
</div>
<div :if={ui_data = @ui_data_backup.ok? && @ui_data_backup.result} class="h-full">
...
Loaded Data
</div>
end
def handle_async(:load_ui_data, {:ok, fetched_data}, socket) do
%{ui_data: ui_data} = socket.assigns
data = AsyncResult.ok(ui_data, fetched_data)
result = data.result
socket =
if Map.has_key?(result, :event_action) do
case result.event_action do
"set_position" ->
socket
|> push_event("set_position", result.single_position)
"loadMapCluster" ->
socket
|> push_event("loadMapCluster", result.map_data)
_ ->
socket
end
else
socket
end
# hack timer to reset assign
Process.send_after(self(), :update_timer, 250)
{:noreply, assign(socket, :ui_data, data)}
end
def handle_async(:load_ui_data, {:exit, reason}, socket) do
%{ui_data: ui_data} = socket.assigns
{:noreply, assign(socket, :ui_data, AsyncResult.failed(ui_data, {:exit, reason}))}
end
def handle_info(:update_timer, socket) do
data = socket.assigns.ui_data
result = data.result
socket =
if Map.has_key?( result, :event_action) do
case result.event_action do
"set_position" ->
socket
|> push_event("set_position", result.single_position)
"loadMapCluster" ->
socket
|> push_event("loadMapCluster", result.map_data)
_ ->
socket
end
else
socket
end
# Update the variable when the timer completes
{:noreply,
socket
|> assign(ui_data_backup: data)
}
end
the async function is working correctly but even if the async variable is assigned the liveview doesn’t (always) update.
With the hack we made, setting a timer to reload another variable, which behaves exactly the same, works 100% of the time…
With one widget ( one additional liveview ) the issue of not updating happens rarely, but if we have more widgets ( more liveviews ) in the same page the problem gets more and more frequent.
With the hack shown above all widgets/liveview update consistently.