Hex.pmDocs

Status: Early development. Not yet ready for production use.

Dala

Dala is a native mobile framework for Elixir and Erlang powered by the BEAM VM.

It brings OTP, lightweight processes, fault tolerance, and the actor model to iOS and Android development while using a Rust-powered native runtime for rendering and platform integration.

Unlike WebView-based frameworks, Dala focuses on native execution, concurrent application architecture, and local-first intelligent applications.

Why Dala?

Modern mobile applications are becoming increasingly complex:

These problems look more like distributed systems than traditional frontend applications.

Dala uses the strengths of the BEAM ecosystem to solve them naturally.

Features

Architecture

Elixir/Erlang
        ↓
     BEAM VM
        ↓
Rust Native Runtime
        ↓
iOS / Android

Dala keeps the BEAM runtime as the core execution engine while using Rust for performance-critical systems such as rendering, layout, native APIs, and ML integrations.

Vision

Dala is not trying to be another web wrapper for mobile apps.

The goal is to build an OTP-native runtime for modern mobile applications — especially apps that require:

Status

Dala is experimental and evolving rapidly.

Installation

Add to mix.exs:

def deps do
  [{:dala, "~> 0.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.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.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.Screen.dispatch(pid, "tap", %{"tag" => "increment"})
  assert Dala.Screen.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

MPL-2.0