MobNew.ProjectGenerator (mob_new v0.1.40)

Copy Markdown View Source

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, for System.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

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

assigns(app_name, opts \\ [])

@spec assigns(
  String.t(),
  keyword()
) :: map()

Returns the EEx template assigns map for app_name.

Options:

  • :local — when true, generates path: deps pointing to local mob/mob_dev repos instead of hex version constraints. Paths are resolved from the MOB_DIR and MOB_DEV_DIR environment variables, falling back to ../mob and ../mob_dev relative to the generated project location.

bundle_prefix()

@spec bundle_prefix() :: String.t()

generate(app_name, dest_dir \\ ".", opts \\ [])

@spec generate(String.t(), String.t(), keyword()) ::
  {:ok, String.t()} | {:error, term()}

Generates a new project at dest_dir/<app_name> from the bundled templates.

Returns {:ok, project_dir} or {:error, reason}.

liveview_generate(app_name, dest_dir \\ ".", opts \\ [])

@spec liveview_generate(String.t(), String.t(), keyword()) ::
  {:ok, String.t()} | {:error, term()}

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.exs to add the mob / mob_dev dependencies
  • Copies the standard Android/iOS native boilerplate
  • Applies the mix mob.enable liveview patches:
    • Injects MobHook into assets/js/app.js
    • Injects the bridge <div> into root.html.heex
    • Generates lib/<app>/mob_screen.ex
    • Writes mob.exs with liveview_port: 4000
  • Patches lib/<app>/application.ex to start Mob.App alongside Phoenix

Returns {:ok, project_dir} or {:error, reason}.

liveview_phoenix_owned?(path, root, opts)

@spec liveview_phoenix_owned?(String.t(), String.t(), keyword()) :: boolean()

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.