Extension points plugins register on the router at runtime — typically
from their lifecycle.on_start, which runs before the app's own
on_start starts the root screen. Nothing to configure in the app.
:before_navigate
Called with the destination (a screen module or a route atom) before a
navigation that mounts a screen — push_screen, reset_to (either
form). Pops and pop_to go back to screens already mounted and don't
call it. The hook returns:
:ok— navigate as usual.{:redirect, module}— mountmoduleinstead (e.g. a "please update" screen), with the same kind of navigation: a push still pushes, so BACK returns to the screen underneath.{:reset, module}— mountmodulewith empty params and replace all navigation with it, whatever the original action was: every stack and tab is discarded, persisted screen state is cleared and the history is empty, asreset_to(socket, module, stack: :all)does. BACK then leaves the app instead of revealing a user screen. Ifmodulefails to resolve or mount, navigation is left untouched and the current screen repaints.{:error, reason}— refuse: navigation is left untouched and the current screen repaints, as for an unknown destination.
Hooks run in the order registered; the first non-:ok answer wins. A
hook that raises, exits, or returns anything else counts as
{:error, _}. It runs in the router process, so it must be fast when
there's nothing to do — a hook that has to fetch code (mob_deliver's
just-in-time screens) blocks navigation while it does.
:after_first_render
Called once per VM, in its own process, after the app's first frame has
actually been handed to the native layer: the root screen's render/1
returned, and Mob.Sender committed the resulting tree (set_root)
without raising. The router's own first paint is asynchronous, so this is
the first point at which "the app came up" is true rather than hoped for.
A root screen whose render/1 raises never triggers it; if a restart of
that screen later renders successfully, it fires then. If the root screen
navigates away before its first paint, the first committed frame is the
destination's. Nothing fires in a VM that never commits a frame, including
test routers that don't render (Mob.Router.start_link/3).
The module of the screen whose frame was committed is appended to the
hook's arguments (nil only while a hot code push has mixed old and new
framework modules). A plugin that needs a frame from one particular
screen — mob_deliver ends an update's probation only on the app's real
root, not on its own "please update" screen — calls rearm_first_render/0
from its hook when the screen isn't the one it wants: the next committed
frame, from any screen, fires the hook again.
Mob.Router.Hooks.register(:before_navigate, {MyPlugin, :before_navigate, []})
Mob.Router.Hooks.register(:after_first_render, {MyPlugin, :first_render, []})
# MyPlugin.before_navigate(destination), MyPlugin.first_render(screen_module)The destination is appended to a :before_navigate hook's arguments, and
the committed screen's module to an :after_first_render hook's.
Summary
Functions
Makes :after_first_render fire again for the next frame committed, from
any screen, with that screen's module.
Registers mfa for hook. Registering the same MFA twice is a no-op.
Removes mfa from hook.
Types
Functions
@spec rearm_first_render() :: :ok
Makes :after_first_render fire again for the next frame committed, from
any screen, with that screen's module.
For a plugin waiting for a frame from a particular screen: call it from the hook when the screen that rendered isn't the one you're waiting for. Until then the hook has fired and stays quiet.
Registers mfa for hook. Registering the same MFA twice is a no-op.
Removes mfa from hook.