Dala.Component behaviour (dala v0.0.1)

Copy Markdown View Source

Behaviour for native view components.

A component is a stateful Elixir process paired with a platform-native view registered by name on iOS/Android. The BEAM owns the state; the native side owns the rendering.

Lifecycle

  1. The parent screen declares Dala.UI.native_view(MyComponent, id: :my_id, ...)
  2. On first render, a Dala.ComponentServer process is started and mount/2 is called
  3. render/1 is called to get the props map forwarded to the native factory
  4. On subsequent renders, update/2 is called with new props from the parent
  5. When the native view fires an event, handle_event/3 is called
  6. After any state change, render/1 is re-called and native props are updated
  7. When the component leaves the tree, the process is stopped and terminate/2 is called

Usage

defmodule MyApp.ChartComponent do
  use Dala.Component

  def mount(props, socket) do
    {:ok, Dala.Socket.assign(socket, :data, props[:data])}
  end

  def render(assigns) do
    %{data: assigns.data}
  end

  def handle_event("segment_tapped", %{"index" => i}, socket) do
    {:noreply, Dala.Socket.assign(socket, :selected, i)}
  end
end

Native registration

Register the view factory at app startup:

# iOS (Swift) — strip "Elixir." prefix and replace "." with "_":
dalaNativeViewRegistry.shared.register("MyApp_ChartComponent") { props, send in
    AnyView(ChartView(data: props["data"]) { index in
        send("segment_tapped", ["index": index])
    })
}

# Android (Kotlin):
dalaNativeViewRegistry.register("MyApp_ChartComponent") { props, send ->
    ChartView(data = props["data"]) { index ->
        send("segment_tapped", mapOf("index" to index))
    }
}

Declaration

Dala.UI.native_view(MyApp.ChartComponent, id: :revenue_chart, data: @points)

The :id must be unique per screen. Duplicate ids on the same screen raise at render time.

Stateless components

If a component has no internal state, omit mount/2 and handle_info/2. The default handle_event/3 raises for any event — add clauses for the events your native view fires, or delegate to the parent screen by forwarding via send/2.

Summary

Functions

Walk a node tree, expanding :native_view nodes into serialisable form.

Callbacks

handle_event(event, payload, socket)

(optional)
@callback handle_event(event :: String.t(), payload :: map(), socket :: Dala.Socket.t()) ::
  {:noreply, Dala.Socket.t()}

handle_info(message, socket)

(optional)
@callback handle_info(message :: term(), socket :: Dala.Socket.t()) ::
  {:noreply, Dala.Socket.t()}

mount(props, socket)

@callback mount(props :: map(), socket :: Dala.Socket.t()) ::
  {:ok, Dala.Socket.t()} | {:error, term()}

render(assigns)

@callback render(assigns :: map()) :: map()

terminate(reason, socket)

(optional)
@callback terminate(reason :: term(), socket :: Dala.Socket.t()) :: term()

update(props, socket)

(optional)
@callback update(props :: map(), socket :: Dala.Socket.t()) :: {:ok, Dala.Socket.t()}

Functions

expand(tree, screen_pid, platform)

@spec expand(map(), pid(), atom()) :: {map(), MapSet.t()}

Walk a node tree, expanding :native_view nodes into serialisable form.

Starts or updates component processes, collects their rendered props, and injects the NIF handle. Returns {expanded_tree, active_keys} where active_keys is a MapSet of {id, module} pairs seen in this render — used by the screen to stop components that have left the tree.