Generates a new Mob project from EEx templates in priv/templates/mob.new/.
Naming conventions
Given app_name = "my_cool_app" and the default bundle prefix:
module_name→"MyCoolApp"display_name→"MyCoolApp"bundle_id→"com.example.my_cool_app"java_package→"com.example.my_cool_app"lib_name→"mycoolapp"(no underscores, forSystem.loadLibrary)java_path→"com/example/my_cool_app"(for directory structure)
Bundle prefix
The reverse-DNS prefix for the bundle ID defaults to com.example, the
universal "must change before shipping" placeholder. Override at generation
time with the MOB_BUNDLE_PREFIX env var:
MOB_BUNDLE_PREFIX=net.acme mix mob.new my_cool_app
# → bundle_id = "net.acme.my_cool_app"We deliberately do not use com.mob — that's our reverse-DNS namespace,
and Apple/Google enforce ownership at submission time, so a project that
ships with com.mob.* would have to be renamed before reaching either store.
Summary
Functions
Patches a generated project to enable Pythonx (embedded CPython, iOS + Android).
Returns the EEx template assigns map for app_name.
Generates a new project at dest_dir/<app_name> from the bundled templates.
Generates a LiveView-wrapped Mob project at dest_dir/<app_name>.
When generating a LiveView project, mix phx.new already produced its own
mix.exs, config/, lib/<app>/, lib/<app>_web/, .gitignore, and assets/ — and
those are the correct versions for a Phoenix app (with gettext,
telemetry_metrics, etc.). The native template's same-named files are
written for the bare-Mob path and would clobber Phoenix's, leaving the
project unable to compile.
Functions
Patches a generated project to enable Pythonx (embedded CPython, iOS + Android).
Two patches:
mix.exs— adds{:pythonx, "~> 0.4"}to deps.lib/<app>/python_paths.ex— pure detection module that reads:code.root_dir/0for iOS andMOB_PYTHON_HOME/MOB_PYTHON_DLenv vars (set by Android'sMainActivity) for Android. Returns:desktop/{:ios, paths}/{:android, paths}/{:partial, missing}.
Note: deliberately does NOT patch config/config.exs — :pythonx, :uv_init in compile-time config makes Pythonx.Application.start/2
auto-run uv at boot, which fails on device. The generated app.ex
(when --python is set) inlines the pyproject_toml and calls
Pythonx.Uv.fetch + init only on the :desktop branch.
Mirrors mix mob.enable pythonx (in mob_dev). Idempotent — safe to
run twice. Public for testing.
Returns the EEx template assigns map for app_name.
Options:
:local— whentrue, generatespath:deps pointing to local mob/mob_dev repos instead of hex version constraints. Paths are resolved from theMOB_DIRandMOB_DEV_DIRenvironment variables, falling back to../moband../mob_devrelative to the generated project location.
@spec bundle_prefix() :: String.t()
Generates a new project at dest_dir/<app_name> from the bundled templates.
Returns {:ok, project_dir} or {:error, reason}.
Generates a LiveView-wrapped Mob project at dest_dir/<app_name>.
This calls mix phx.new as a subprocess to create the Phoenix project, then:
- Patches
mix.exsto add the mob / mob_dev dependencies - Copies the standard Android/iOS native boilerplate
- Applies the
mix mob.enable liveviewpatches:- Injects
MobHookintoassets/js/app.js - Injects the bridge
<div>intoroot.html.heex - Generates
lib/<app>/mob_screen.ex - Writes
mob.exswithliveview_port: 4000
- Injects
- Patches
lib/<app>/application.exto startMob.Appalongside Phoenix
Returns {:ok, project_dir} or {:error, reason}.
When generating a LiveView project, mix phx.new already produced its own
mix.exs, config/, lib/<app>/, lib/<app>_web/, .gitignore, and assets/ — and
those are the correct versions for a Phoenix app (with gettext,
telemetry_metrics, etc.). The native template's same-named files are
written for the bare-Mob path and would clobber Phoenix's, leaving the
project unable to compile.
When :liveview is true in opts, this predicate returns true for any
template path that Phoenix already owns, so the copy step skips it. Files
that are unique to Mob (mob.exs, src/<app>.erl, android/, ios/) still get
emitted normally.
Public for testing — guards against the regression where a new template path lands in the native tree without being added to the LiveView blocklist.