mob_nfc

NFC for Mob apps — read and write NDEF tags, read raw tag UIDs, and emulate a tag (Android HCE). iOS CoreNFC (iPhone 7+) / Android NfcAdapter + HostApduService.

CapabilityAPIiOSAndroid
Read NDEFstart_reading/2
Read raw tag (UID/type)start_reading(mode: :tag)
Write NDEFwrite_ndef/3
Emulate a tag (HCE)emulate_ndef/3— (unsupported)✅ read + writable

All paths are device-verified (iPhone SE 3rd gen ↔ Moto G power 5G 2024).

Install

# mix.exs
{:mob_nfc, github: "GenericJam/mob_nfc"}
# mob.exs — plugins are opt-in (a bare deps.get does nothing)
config :mob, :plugins, [:mob_nfc]

Use

Calls return the socket unchanged; results arrive in handle_info/2 as {:nfc, ...} messages to the process that started the session.

# Read
def handle_event("scan", _p, socket), do: {:noreply, MobNfc.start_reading(socket)}
def handle_info({:nfc, :ndef, %{ndef: bytes}}, socket) do
records = MobNfc.Ndef.parse(bytes) # one tested parser, shared across platforms
uris = for r <- records, {:ok, u} <- [MobNfc.Ndef.decode_uri(r)], do: u
{:noreply, assign(socket, :uris, uris)}
end
# Write
MobNfc.write_ndef(socket, MobNfc.Ndef.uri_record("https://mob.dev"))
MobNfc.write_ndef(socket, [MobNfc.Ndef.text_record("hi"), uri_rec])
# Emulate a tag (Android)
MobNfc.emulate_ndef(socket, MobNfc.Ndef.uri_record("https://mob.dev"))
MobNfc.emulate_ndef(socket, rec, writable: true) # a reader can write into it

Events (tagged :nfc)

{:nfc, :session_started}
{:nfc, :ndef, %{tag_id: binary, ndef: binary, writable: boolean, max_size: integer}}
{:nfc, :tag, %{tag_id: binary, tech: binary}} # a tag with no NDEF data
{:nfc, :written, %{bytes: integer}} # write_ndef/3 succeeded
{:nfc, :emulation_started | :emulation_stopped} # HCE lifecycle
{:nfc, :hce_read} # a reader read the emulated tag
{:nfc, :hce_written, %{ndef: binary}} # a reader wrote to it (writable HCE)
{:nfc, :session_ended, reason} # :done | :user_cancel | :timeout | ...
{:nfc, :error, reason} # :unavailable | :read_only | :too_small | ...

ndef is the raw NDEF message bytes — turn it into records with MobNfc.Ndef.parse/1 and decode Text/URI with decode_text/1 / decode_uri/1. MobNfc.available?/0 reports whether the radio is present and enabled; reading is a no-op on the simulator/emulator.

Host setup

The plugin adds what it can automatically (Android NFC permission, iOS NFCReaderUsageDescription). A few things mob_dev can't contribute yet must be added by hand — the native build prints them as host_requirements:

See the plugin manifest host_requirements for the exact XML snippets.

Design notes