Packages
mob
0.9.17
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.1
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.39
0.7.38
0.7.37
0.7.36
0.7.35
0.7.34
0.7.33
0.7.32
0.7.31
0.7.30
0.7.29
0.7.28
0.7.27
0.7.26
0.7.25
0.7.24
0.7.23
0.7.22
0.7.21
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
usage-rules.md
# Mob usage rules
Rules for writing a Mob app (Elixir on the phone, native SwiftUI/Compose UI).
Short on purpose. Each rule names its guide: `deps/mob/guides/<name>.md` in an
app (the version in `mix.lock`), or https://hexdocs.pm/mob/<name>.html.
## Where the docs are
- `deps/mob/guides/*.md` and this file: the guides for exactly the mob version
the app uses (shipped in the package since mob 0.9.17).
- https://hexdocs.pm/mob/llms.txt: index of every guide and module. Each
page is also available as Markdown: https://hexdocs.pm/mob/screen_lifecycle.md
- `mix hex.docs fetch mob`: offline copy under `~/.hex/docs/hexpm/mob/<version>/`.
- IEx: `h Mob.Socket.start_async`, `h Mob.Screen`.
- Coming from Phoenix LiveView? Start with `deps/mob/guides/coming_from_liveview.md`.
## Screens
- A screen is `use Mob.Screen`: a process holding a socket. Callbacks are
`mount/3`, `render/1`, `handle_info/2`, `handle_async/3`, `handle_event/3`,
`terminate/2`. See `deps/mob/guides/screen_lifecycle.md`.
- Update assigns with `Mob.Socket.assign/2,3`, `update/3`, `assign_new/3`.
They aren't imported by `use Mob.Screen`; call them on `Mob.Socket`.
- Render with the `~MOB` sigil: `@assigns`, `:if={...}` and `:for={...}`
work as in HEEx. Keep `render/1` pure. See `deps/mob/guides/components.md`.
- Taps, text changes and other native input arrive in `handle_info/2`
(`on_tap={{self(), :save}}` → `{:tap, :save}`). Define a catch-all
`handle_info(_msg, socket)` clause. See `deps/mob/guides/events.md`.
## Async loading
- `mount/3` runs before the first frame: never do slow work (HTTP, big
queries) there. Assign a placeholder and use
`Mob.Socket.start_async(socket, name, fun)`. The result arrives in
`handle_async(name, {:ok, result} | {:exit, reason}, socket)`.
- Don't use a bare `Task.async/1` in a screen: its reply, `:DOWN` and
`:EXIT` all land in `handle_info/2`. `start_async/3` handles them, stops
the task with the screen, and keeps only the newest task per name.
- See `deps/mob/guides/screen_lifecycle.md` (section `handle_async/3`).
## Lists
- Long or growing lists use `<LazyList>`, which builds only the visible rows.
There is no LiveView-style `stream`; keep the list in assigns.
- Paginate with `<LazyList on_end_reached={{self(), :load_more}}>`. It
delivers `{:tap, :load_more}` (not `{:end_reached, _}`) and only works on
`<LazyList>`. Handlers must be idempotent. See `deps/mob/guides/events.md`
(Infinite scroll).
- Give rows a stable `:id`.
## Navigation
- `Mob.Socket.push_screen/3`, `pop_screen/1`, `pop_to/2`, `reset_to/4`,
`switch_tab/2` return a socket; the navigation happens after the callback
returns. See `deps/mob/guides/navigation.md`.
## Testing
- Unit-test screens with `Mob.ScreenCase`, no device needed: `mount_screen/4`,
`render_info/2` (a tap), `render_event/3`, `render_async/2` (await
`start_async` tasks), plus `assigns/1`, `find/3`, `text/1` and
`assert_renderable/2`. See `deps/mob/guides/testing.md`.
- On a running app, `Mob.Test` reads state and drives it over Erlang
distribution (`Mob.Test.assigns/1`, `Mob.Test.tap/2`). Prefer it to
screenshots. See `deps/mob/guides/agentic_coding.md`.