|
| 1 | +--- |
| 2 | +title: 'Where the antenna is, and what the settings allow' |
| 3 | +sidebarTitle: 'Antenna and secure NFC' |
| 4 | +description: 'Two things only the device can answer: where its NFC antenna physically sits, and whether NFC is restricted to an unlocked screen.' |
| 5 | +--- |
| 6 | + |
| 7 | +Both of these describe the hardware in the user's hand rather than anything your app |
| 8 | +does with it. They exist because the two most common complaints about an NFC screen — |
| 9 | +"it never reads" and "nothing happens on the lock screen" — usually have nothing to do |
| 10 | +with the tag. |
| 11 | + |
| 12 | +## Where to tell the user to tap |
| 13 | + |
| 14 | +Android 14 added an API that reports where the NFC antenna physically sits on the |
| 15 | +device. It is the difference between a card illustration in the middle of the screen |
| 16 | +and one over the antenna. |
| 17 | + |
| 18 | +```ts |
| 19 | +import { nfc } from 'react-native-nfc-kit'; |
| 20 | + |
| 21 | +const info = await nfc.getAntennaInfo(); |
| 22 | + |
| 23 | +if (info !== null) { |
| 24 | + const [antenna] = info.antennas; |
| 25 | + // Millimetres from the bottom-left corner of the device. |
| 26 | + console.log(antenna.locationX, antenna.locationY); |
| 27 | +} |
| 28 | +``` |
| 29 | + |
| 30 | +<Info> |
| 31 | + The coordinates are millimetres from the **bottom-left corner of the device** — the corner of the |
| 32 | + hardware, bezels included. They are not screen coordinates and not pixels, so they mean nothing on |
| 33 | + their own. That is why `deviceWidth` and `deviceHeight` come back in the same object: it is the |
| 34 | + ratio of the two that becomes a position you can lay out against. |
| 35 | +</Info> |
| 36 | + |
| 37 | +```ts |
| 38 | +import { nfc } from 'react-native-nfc-kit'; |
| 39 | + |
| 40 | +const info = await nfc.getAntennaInfo(); |
| 41 | +const antenna = info?.antennas[0]; |
| 42 | + |
| 43 | +const hint = |
| 44 | + info === null || antenna === undefined |
| 45 | + ? null |
| 46 | + : { |
| 47 | + left: `${(antenna.locationX / info.deviceWidth) * 100}%`, |
| 48 | + // Measured up from the bottom, which is the opposite of a CSS `top`. |
| 49 | + bottom: `${(antenna.locationY / info.deviceHeight) * 100}%`, |
| 50 | + }; |
| 51 | +``` |
| 52 | + |
| 53 | +A phone can report more than one antenna, and `antennas` is ordered as the platform |
| 54 | +gives it, with no promise about which is the "main" one. Most devices report exactly |
| 55 | +one. |
| 56 | + |
| 57 | +### When it is null |
| 58 | + |
| 59 | +`null` is a normal answer, not a failure, and it covers four different situations on |
| 60 | +purpose: |
| 61 | + |
| 62 | +| Situation | Why | |
| 63 | +| -------------------------------------- | ----------------------------------------------------------- | |
| 64 | +| iOS | CoreNFC never reports antenna geometry, at any iOS version. | |
| 65 | +| Web | A page is handed records, never anything about the radio. | |
| 66 | +| Android 13 and earlier | The API arrived in Android 14 (API 34). | |
| 67 | +| Android 14 where the OEM left it empty | Common. The numbers are the manufacturer's to fill in. | |
| 68 | + |
| 69 | +The last row is the one worth designing around: it means the `null` branch is not "the |
| 70 | +iOS branch" by another name, and a device on the right Android version can still |
| 71 | +decline to answer. Keep the generic illustration as the fallback. |
| 72 | + |
| 73 | +Because of that, `getAntennaInfo()` resolves `null` rather than rejecting — a screen |
| 74 | +laying out a hint needs no `try`/`catch`. If you want the answer without the round |
| 75 | +trip, `nfc.capabilities?.antennaInfo` is the same fact as a boolean. |
| 76 | + |
| 77 | +<Note> |
| 78 | + On a foldable, `deviceFoldable` is `true` and every number describes the device **unfolded**. A |
| 79 | + folded phone needs its own arithmetic before any of this reaches a layout, and the platform gives |
| 80 | + you nothing to do it with — so on a foldable, showing the generic hint is usually the better |
| 81 | + answer. |
| 82 | +</Note> |
| 83 | + |
| 84 | +## Secure NFC |
| 85 | + |
| 86 | +Android 10 added a setting called **Secure NFC**: when it is on, the device reads tags |
| 87 | +only while the screen is unlocked. It is off by default on most devices and on by |
| 88 | +default on a few, and a user can turn it on without connecting it to anything. |
| 89 | + |
| 90 | +```ts |
| 91 | +import { nfc } from 'react-native-nfc-kit'; |
| 92 | + |
| 93 | +if (await nfc.isSecureNfcEnabled()) { |
| 94 | + // A tap on the lock screen will do nothing at all. Say so, rather than |
| 95 | + // letting the scan sit there looking broken. |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +This is a call rather than a capability because the user can change it from the |
| 100 | +settings while your app is running. Whether the device has the setting at all is |
| 101 | +`nfc.capabilities?.secureNfc`, which is `false` below Android 10, on iOS and on the |
| 102 | +web — and `isSecureNfcEnabled()` is `false` there too. |
| 103 | + |
| 104 | +It matters most for [background and launch tags](/setup/background-reading): a tap |
| 105 | +against a locked phone is exactly what that feature is for, and Secure NFC is the |
| 106 | +reason it silently does nothing on some devices. A reading session started from a |
| 107 | +screen the user is looking at is unaffected, because the screen is unlocked by |
| 108 | +definition. |
| 109 | + |
| 110 | +## What each platform reports |
| 111 | + |
| 112 | +| | iOS | Android | Web (Chrome) | |
| 113 | +| ---------------------- | :-----: | :-----------------------------------------: | :----------: | |
| 114 | +| `getAntennaInfo()` | `null` | API 34+, and only when the OEM filled it in | `null` | |
| 115 | +| `isSecureNfcEnabled()` | `false` | API 29+ | `false` | |
| 116 | + |
| 117 | +Neither call needs a permission, an entitlement, or anything in the manifest. Both are |
| 118 | +safe to call on every platform on startup. |
0 commit comments