Packages
mob
0.6.13
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.26
0.6.25
0.6.24
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.2
0.6.1
0.6.0
0.5.18
0.5.17
0.5.16
0.5.15
0.5.14
0.5.11
0.5.10
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
0.1.0
BEAM-on-device mobile framework for Elixir
Current section
Files
Jump to
Current section
Files
lib/mob/event.ex
defmodule Mob.Event do
@moduledoc """
The unified event emission API for Mob.
See `guides/event_model.md` for the full event model.
Two responsibilities:
1. **Emit** — given an `%Address{}`, an event atom, and a payload, deliver
the canonical envelope `{:mob_event, addr, event, payload}` to the right
pid (resolving in-tree or external targets).
2. **Match** — small helpers (`is_event?/1`, `match_address?/2`) for handler
code that wants to filter incoming events by address fields.
This module is the **single doorway** between native event sources and
user-level handler code. Every emitter (taps, gestures, lifecycle, custom
components) eventually calls `Mob.Event.emit/4` (or `dispatch/4`, for
pre-resolved pids).
"""
alias Mob.Event.{Address, Target}
require Logger
@typedoc "The shape of every event message delivered to a handler."
@type envelope :: {:mob_event, Address.t(), atom(), term()}
@doc """
Resolve `target` and deliver the event.
Best-effort delivery: if the target can't be resolved (dead pid, unknown
registered name, component not in the ancestor chain), logs a debug
message and drops the event.
Returns `:ok` always. Errors from resolution are logged, not raised — losing
one event from a stale handle should never crash the BEAM.
"""
@spec emit(Address.t(), atom(), term(), Target.spec(), Target.render_scope()) :: :ok
def emit(%Address{} = addr, event, payload, target, scope)
when is_atom(event) do
case Target.resolve(target, scope) do
{:ok, pid} ->
dispatch(pid, addr, event, payload)
{:error, reason} ->
Logger.debug(fn ->
"[Mob.Event] dropping #{event} for #{Address.to_string(addr)}: #{inspect(reason)}"
end)
:ok
end
end
@doc """
Send the event to an already-resolved pid.
Used when the renderer pre-resolved the target at registration time and
passes the pid directly. Skips re-resolution.
Also broadcasts to any `Mob.Event.Trace` subscribers (zero cost when no
tracers are registered).
"""
@spec dispatch(pid(), Address.t(), atom(), term()) :: :ok
def dispatch(pid, %Address{} = addr, event, payload) when is_pid(pid) and is_atom(event) do
send(pid, {:mob_event, addr, event, payload})
Mob.Event.Trace.broadcast(addr, event, payload)
:ok
end
@doc """
True if `msg` matches the canonical event envelope shape.
iex> Mob.Event.is_event?({:mob_event, %Mob.Event.Address{screen: S, widget: :x, id: :y}, :tap, nil})
true
iex> Mob.Event.is_event?({:tap, :something})
false
"""
@spec is_event?(term()) :: boolean()
def is_event?({:mob_event, %Address{}, event, _payload}) when is_atom(event), do: true
def is_event?(_), do: false
@doc """
True if `addr` matches all the given filters.
Filters is a keyword list of address fields and the values they must equal.
Useful for one-off matches in `handle_info/2` clauses where you don't want
to write a full struct pattern.
iex> addr = %Mob.Event.Address{screen: S, widget: :button, id: :save}
iex> Mob.Event.match_address?(addr, widget: :button)
true
iex> addr = %Mob.Event.Address{screen: S, widget: :button, id: :save}
iex> Mob.Event.match_address?(addr, widget: :button, id: :cancel)
false
"""
@spec match_address?(Address.t(), keyword()) :: boolean()
def match_address?(%Address{} = addr, filters) when is_list(filters) do
Enum.all?(filters, fn {k, v} -> Map.get(addr, k) == v end)
end
# ── Test helpers ──────────────────────────────────────────────────────────
@doc """
Synthesize an event delivery to `pid`. Useful for tests — bypasses the
native side entirely.
Mob.Event.send_test(self(), MyScreen, :button, :save, :tap, nil)
assert_receive {:mob_event, %Address{id: :save}, :tap, nil}
"""
@spec send_test(pid(), atom() | pid(), atom(), Address.id(), atom(), term(), keyword()) :: :ok
def send_test(pid, screen, widget, id, event, payload \\ nil, opts \\ []) do
addr =
Address.new(
Keyword.merge(
[
screen: screen,
widget: widget,
id: id
],
opts
)
)
dispatch(pid, addr, event, payload)
end
end