# `Mob.Notification`
[🔗](https://github.com/genericjam/mob/blob/master/lib/mob/notification.ex#L1)

The `{:notification, notification}` message a screen receives when a
notification arrives or is tapped, on both platforms.

    def handle_info({:notification, %{presentation: :tap, data: data}}, socket) do
      {:noreply, Mob.Socket.push_screen(socket, MyApp.ThreadScreen, data)}
    end

    # Arrived while the app was open; the OS still shows its banner.
    def handle_info({:notification, %{presentation: :foreground}}, socket) do
      {:noreply, socket}
    end

## Fields

  * `:presentation` — `:foreground` when the notification arrived while the
    app was in the foreground, `:tap` when the user opened it from the
    banner or notification centre, including the tap that launched the app
    from a killed state.
  * `:action` — for a `:tap`, `"default"` for a tap on the notification
    itself. An app that registers its own notification categories also sees
    `"dismiss"` and its own action identifiers here (iOS). `nil` for
    `:foreground`.
  * `:source` — `:push` for a remote notification, `:local` for one
    scheduled on the device.
  * `:id` — the notification's identifier (the `id:` given to
    `MobNotify.schedule/2`), or `nil`.
  * `:title`, `:body` — the displayed text, or `nil`.
  * `:data` — the custom payload: `data:` from `MobNotify.schedule/2`, or
    the custom keys of a push payload (iOS's `aps` dictionary is not
    included). Top-level keys are atoms; nested values are as JSON decodes
    them (maps with string keys, lists, numbers, booleans, `nil`).

## Where it is delivered

To the process that registered for notifications through `mob_notify`
(`MobNotify.register_push/1`, or `MobNotify.schedule/2` on iOS) while it is
alive. Otherwise to the screen currently showing. A tap that launches the
app arrives at the root screen once it has mounted, exactly once.

Each native layer serialises the notification to one JSON envelope and hands
it to the `:mob_screen` router, which decodes it with `decode/1`. That keeps
the shape in one place rather than one per platform; see
`decisions/2026-10-01-notification-delivery-envelope.md`.

# `t`

```elixir
@type t() :: %{
  id: String.t() | nil,
  title: String.t() | nil,
  body: String.t() | nil,
  data: map(),
  source: :local | :push,
  presentation: :foreground | :tap,
  action: String.t() | nil
}
```

# `decode`

```elixir
@spec decode(binary()) ::
  {:ok, t()} | {:error, :invalid_json | :not_an_object | :data_key_too_long}
```

Decodes the JSON envelope native code hands to the router.

`presentation` defaults to `"tap"` when absent. Most callers that predate
the field send taps: app-owned Android `MainActivity` code and callers of
the iOS `mob_set_launch_notification_json/1` hook. The exception is the
app-owned `MobFirebaseService` that mob_new 0.1.45–0.4.10 generated, which
hands over foreground pushes without it; such an app must add
`"presentation": "foreground"` (see the push notifications guide).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
