Dala

BEAM-on-device dalaile framework for Elixir. OTP runs inside your iOS and Android apps — embedded directly in the app bundle, no server required. Screens are GenServers; the UI is rendered by Compose and SwiftUI via a thin NIF.

Hex.pmDocs

Status: Early development. Android emulator and iOS simulator confirmed working. Not yet ready for production use.

original repo mob

What it is

Your Elixir app (GenServers, OTP supervision, pattern matching, pipes)
          ↓
     Dala.Screen  (GenServer — your logic lives here)
          ↓
    Dala.Renderer  (component tree → JSON → NIF call)
          ↓
Compose (Android)   SwiftUI (iOS)   ← native rendering, native gestures

You write Elixir. The native layer handles rendering. The BEAM node runs on the device — connect your dev machine to the running app over Erlang distribution, inspect state, and hot-push new bytecode without a restart.

Installation

Add to mix.exs:

def deps do
  [{:dala, "~> 0.5"}]
end

The dala_new package (separate) provides project generation, deployment tooling, and will import dala_dev which is a live dashboard. Install it as a Mix archive:

mix archive.install hex dala_new

A screen

defmodule MyApp.CounterScreen do
  use Dala.Screen

  def mount(_params, _session, socket) do
    {:ok, Dala.Socket.assign(socket, :count, 0)}
  end

  def render(assigns) do
    %{
      type: :column,
      props: %{padding: :space_md, gap: :space_md, background: :background},
      children: [
        %{type: :text,   props: %{text: "Count: #{assigns.count}", text_size: :xl, text_color: :on_background}, children: []},
        %{type: :button, props: %{text: "Increment", on_tap: {self(), :increment}}, children: []}
      ]
    }
  end

  def handle_event("tap", %{"tag" => "increment"}, socket) do
    {:noreply, Dala.Socket.assign(socket, :count, socket.assigns.count + 1)}
  end
end

App entry point

defmodule MyApp do
  use Dala.App, theme: Dala.Theme.Obsidian

  def navigation(_platform) do
    stack(:home, root: MyApp.CounterScreen)
  end

  def on_start do
    Dala.Screen.start_root(MyApp.CounterScreen)
    Dala.Dist.ensure_started(node: :"my_app@127.0.0.1", cookie: :secret)
  end
end

Navigation

# Push a new screen
Dala.Socket.push_screen(socket, MyApp.DetailScreen, %{id: 42})

# Pop back
Dala.Socket.pop_screen(socket)

# Tab bar layout
tab_bar([
  stack(:home,    root: MyApp.HomeScreen,    title: "Home"),
  stack(:profile, root: MyApp.ProfileScreen, title: "Profile")
])

Theming

# Named theme
use Dala.App, theme: Dala.Theme.Obsidian

# Override individual tokens
use Dala.App, theme: {Dala.Theme.Obsidian, primary: :rose_500}

# From scratch
use Dala.App, theme: [primary: :emerald_500, background: :gray_950]

# Runtime switch (accessibility, user preference)
Dala.Theme.set(Dala.Theme.Citrus)

Built-in themes: Dala.Theme.Obsidian (dark violet), Dala.Theme.Citrus (warm charcoal + lime), Dala.Theme.Birch (warm parchment).

Device APIs

All async — call the function, handle the result in handle_info/2:

# Haptic feedback (synchronous — no handle_info needed)
Dala.Haptic.trigger(socket, :success)

# Camera
Dala.Camera.capture_photo(socket)
def handle_info({:camera, :photo, %{path: path}}, socket), do: ...

# Location
Dala.Location.start(socket, accuracy: :high)
def handle_info({:location, %{lat: lat, lon: lon}}, socket), do: ...

# Push notifications
Dala.Notify.register_push(socket)
def handle_info({:push_token, :ios, token}, socket), do: ...

Also: Dala.Clipboard, Dala.Share, Dala.Photos, Dala.Files, Dala.Audio, Dala.Motion, Dala.Biometric, Dala.Scanner, Dala.Permissions.

Live development

mix dala.connect          # tunnel + connect IEx to running device
nl(MyApp.SomeScreen)     # hot-push new bytecode, no restart

# In IEx:
Dala.Test.screen(:"my_app_ios@127.0.0.1")  #=> MyApp.CounterScreen
Dala.Test.assigns(:"my_app_ios@127.0.0.1") #=> %{count: 3, ...}
Dala.Test.tap(:"my_app_ios@127.0.0.1", :increment)

Testing

test "increments count" do
  {:ok, pid} = Dala.Screen.start_link(MyApp.CounterScreen, %{})
  :ok = Dala.Screen.dispatch(pid, "tap", %{"tag" => "increment"})
  assert Dala.Screen.get_socket(pid).assigns.count == 1
end

Related packages

Package Purpose
dala_dev Dev tooling: mix dala.new, mix dala.deploy, mix dala.connect, live dashboard
dala_push Server-side push notifications (APNs + FCM)

Documentation

Full documentation at hexdocs.pm/dala, including:

License

MIT