Practical NFC Integration in Mobile Apps
We often encounter situations where 30–40% of initial NFC read attempts fail with NFCReaderError.readerTransceiveErrorTagConnectionLost on iOS or IOException: Tag lost on Android. The cause is typically too brief phone contact, interference from metal surfaces, or an incorrect session timeout. Without a systematic approach, integration becomes endless debugging.
Why do read errors occur?
iOS: CoreNFC limitations
CoreNFC has been around for a while, but full NDEF read/write became available only in later iOS versions. Targeting older versions leaves half of users without NFC. Another hurdle is NFCNDEFReaderSession vs. NFCTagReaderSession. The former works only with NDEF-compatible tags. If the client brings a Mifare Classic, it simply won't work – Apple does not support this protocol for security reasons. You need to check the tag type in advance and use NFCTagReaderSession with NFCMiFareTag for Mifare Ultralight or NFCISO7816Tag for smart cards.
Another bottleneck is Background Tag Reading. The app is not running, the user taps a tag – the system reads an NDEF URI and launches the app. That seems magical, but requires com.apple.developer.nfc.readersession.formats in entitlements with NDEF, plus a URL scheme or Universal Link. If you forget to add the domain to apple-app-site-association, the deep link simply won't open.
Android: NFC stack fragmentation
On Android, NfcAdapter.ACTION_NDEF_DISCOVERED, ACTION_TAG_DISCOVERED, ACTION_TECH_DISCOVERED are three different intents with different priorities. If you declare only NDEF, a tag without NDEF structure will go to another app. Foreground dispatch via enableForegroundDispatch() solves this but requires a clear lifecycle: enable in onResume, disable in onPause. One missed call and the app starts receiving NFC intents even when not active.
For NDEF writing: if the tag is write-protected or formatted for another type, tag.connect() will hang or throw IOException. You need an explicit timeout via tag.setTimeout() and retry logic with exponential backoff. Our experience shows that proper error handling reduces failures by 3 times.
How we ensure stable NFC reading on both platforms
iOS
We use CoreNFC through NFCNDEFReaderSession for standard scenarios and NFCTagReaderSession for non-standard formats. For writing, we create an NFCNDEFMessage with the necessary NFCNDEFPayload. We use NFCNDEFPayload.wellKnownTypeURIPayload() for URI records – this eliminates TNF header errors. The session is wrapped in async/await via Continuation to avoid delegate chains. Errors are mapped to understandable states – sessionTimeout, tagNotCompatible, writeProtected – and displayed via native alerts or custom UI. Average read time is 0.3 seconds, with 98% of sessions succeeding when held correctly.
Android
We use Ndef and NdefFormatable tech classes via Tag.getTechList(). Before writing, we check ndef.isWritable() and ndef.maxSize() – a common mistake is trying to write 500 bytes to a 144-byte Ntag213. For formatting clean tags, we use NdefFormatable.format() with a minimal initial message.
All NFC code is placed in an NfcRepository with Flow<NfcEvent>, and the UI layer subscribes via collectLatest. Screen rotation does not interrupt the session – foreground dispatch is restored in onResume. This approach has been tested on 15+ projects over 5 years. We guarantee session stability even on complex configurations.
Supported tag types
| Tag type | iOS | Android | Notes |
|---|---|---|---|
| NDEF (Ntag213/215/216) | ✓ | ✓ | Most common |
| Mifare Ultralight | ✓ (ISO7816) | ✓ | Requires NFCTagReaderSession on iOS |
| Mifare Classic | ✗ | ✓ | Not supported by Apple |
| ISO 15693 | ✓ (iOS 14+) | ✓ | For industrial tags |
| FeliCa | ✓ (Japan only) | ✓ | Transit cards |
iOS vs. Android comparison
| Aspect | iOS | Android |
|---|---|---|
| Session stability | High, but strict tag type restrictions | Lower due to fragmentation, but more flexible |
| Tag type support | Limited (no Mifare Classic) | Broad, including all popular types |
| Background reading | iOS 13+, requires entitlements | Android 10+, Beam deprecated |
| Configuration complexity | Higher (entitlements, provisioning) | Lower (manifest + dispatch) |
Common mistakes and how to avoid them
- Tag Lost on iOS: increase timeout, add retry, inform the user.
- Writing to a protected tag: check
isWritable()and notify. - Session interruption on screen rotation on Android: isolate logic in a repository with Flow.
- Tag type incompatibility: audit before development.
What's included in the work
- Audit: determine tag types, data volume, need for background reading, target platforms. This can save up to 20% of the budget by selecting the right stack.
- Design: choose tech classes, architecture (repository with Flow/async/await).
- Implementation: unit tests with mock tags (
NFCNDEFReaderSessionMockon iOS,MockNdefTagon Android). - Testing on real tags from NXP, Broadcom, ST Microelectronics.
- Integration documentation and user instructions.
- Support for 30 days after delivery.
Get a consultation for your project – we'll estimate the scope and timeline. Order an NFC integration audit: we will analyze your tags, platforms, and usage scenarios.
Process flow
- Study – analyze requirements, tag types, usage conditions.
- Design – choose stack, create session prototype.
- Development – implement with tests on emulators and real devices.
- Testing – on 10+ tag samples from different manufacturers.
- Launch – publish to App Store / Google Play, configure background reading.
Timeline and pricing
Basic integration for reading/writing NDEF on a single platform takes 3–5 working days. For custom formats, background reading, or multiple tag types, it takes from 2 weeks. We provide a precise estimate after an audit.
Links:







