
Near Field Communication (NFC) is a short-range wireless technology that lets devices share data when they are close. You find it in contactless payments, smart business cards, and other daily tools that make life easy. For mobile developers, adding an NFC reader and writer to apps opens up many possibilities.
Flutter, Google’s UI toolkit, lets you build apps for mobile, web, and desktop from one codebase. It makes creating cross-platform apps with rich features more efficient. The NFC Manager Flutter package helps implement NFC reader writer features on both Android and iOS. This step-by-step tutorial will show how to build an NFC app in Flutter that can read from and write to NFC tags.
Businesses can also partner with professional Flutter app development services to streamline development and ensure robust NFC functionality.
Project Setup for Your Flutter NFC Project
Before we dive into the code, let’s get our project set up and ready.
Prerequisites for Implementing NFC in Flutter
Make sure you have the following installed and ready:
- Flutter SDK: Install the Flutter SDK by following the official documentation.
- Basic understanding of Dart and Flutter: You need to know the basics of the Dart language and the Flutter framework.
- A physical device that supports NFC: You cannot test Flutter NFC reader writer apps on simulators or emulators. You need an Android or iOS device that supports NFC.
For scaling teams or projects, you can also consider working with dedicated Flutter developers for hire to speed up implementation.

Creating a New Flutter Project
To start, create a new Flutter project by running the following command in your terminal:
flutter create nfc_demo
This will create a new directory named nfc_demo with the basic Flutter project structure. Open this project in your favorite IDE, such as Visual Studio Code or Android Studio.
If you’re new, our Flutter BLoC state management tutorial is a good place to start learning about state handling in apps.
Adding Dependencies
Next, we need to add the nfc_manager package to our project. Open the pubspec.yaml file and add the following line under dependencies:
YAML
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
nfc_manager: ^4.0.2
nfc_manager_ndef: ^1.0.1
Code language: CSS (css)
After adding the dependency, run the following command in your terminal to install it:
Bash
flutter pub get
Code language: JavaScript (javascript)
The nfc_manager Flutter package is the core dependency that provides the necessary APIs to interact with the device’s NFC hardware for reading and writing NFC tags.
Implementing NFC Features
With the project set up, we can now start implementing NFC in Flutter.
Checking for NFC Availability

Before attempting any NFC operation, it’s crucial to check if the device hardware actually supports NFC. This prevents errors and allows you to provide a better user experience by, for example, disabling NFC-related UI elements.
Code Snippet:
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:nfc_manager/ndef_record.dart';
import 'package:nfc_manager/nfc_manager.dart';
import 'package:nfc_manager_ndef/nfc_manager_ndef.dart';
import 'package:nfc_manager/src/nfc_manager_android/pigeon.g.dart';
import 'nfc_scan_result.dart';
// A boolean to check if NFC is available on the device.
bool isNfcAvailable = false;
Future<void> _checkNfcAvailability() async {
setState(() async {
isNfcAvailable = await NfcManager.instance.isAvailable();
});
}
Code language: JavaScript (javascript)
Code Explanation:
- NfcManager.instance.isAvailable(): This asynchronous method returns a Future<void> which resolves to true if the device has NFC hardware and it is enabled, and false otherwise.
Reading NFC Tags
To start reading an NFC tag, you need to initiate a session and listen for tag discovery. This is the first step in building a Flutter NFC reader app.
Code Snippet:
void startRead(BuildContext context) async {
if (_isScanning) return;
setState(() {
_isScanning = true;
_status = 'Hold a tag near the device...';
});
try {
await NfcManager.instance.startSession(
pollingOptions: {NfcPollingOption.iso14443},
onDiscovered: (NfcTag tag) async {
try {
final result = await _readTagData(tag);
// Extract NDEF Text record (RTD-Text) if present
final ndefText = extractNdefText(result);
setState(() {
_status = (ndefText != null && ndefText.isNotEmpty)
? 'Tag read successfully! Text: $ndefText'
: 'Tag read successfully! No valid NDEF text found.';
});
} catch (e) {
setState(() {
_status = 'Error during read: $e';
});
} finally {
await NfcManager.instance.stopSession();
setState(() => _isScanning = false);
}
},
);
} catch (e) {
setState(() {
_status = 'Failed to start NFC session: $e';
_isScanning = false;
});
}
}
String? extractNdefText(NFCScanResult result) {
final ndef = result.data['ndef'];
if (ndef is Map && ndef['records'] is List && ndef['records'].isNotEmpty) {
final record = ndef['records'][0];
if (record is Map && record['type'] != null && record['payload'] != null) {
final typeBytes = record['type'];
final payloadBytes = record['payload'];
if (typeBytes is List && String.fromCharCodes(typeBytes) == 'T' && payloadBytes is List) {
final payload = Uint8List.fromList(payloadBytes);
if (payload.isEmpty) return null;
// NFC Forum Text RTD:
// payload[0] = status byte
// bit7: 0=UTF-8, 1=UTF-16
// bits0..5: language code length (n)
// payload[1..n]: language code
// payload[1+n..]: text
final status = payload;
final isUtf16 = (status & 0x80) != 0;
final langLen = status & 0x3F;
if (payload.length < 1 + langLen) return null;
final textBytes = payload.sublist(1 + langLen);
try {
return isUtf16 ? String.fromCharCodes(textBytes) : utf8.decode(textBytes);
} catch (_) {
return String.fromCharCodes(textBytes);
}
}
}
}
return null;
}
Future<NFCScanResult> _readTagData(NfcTag tag) async {
final result = NFCScanResult(
tag: tag,
timestamp: DateTime.now(),
tagType: 'Unknown',
uid: 'Unknown',
data: {},
);
try {
// Extract UID from common locations in raw map
final raw = tag.data;
List<int>? idBytes;
if (raw is Map && raw['id'] is List) {
idBytes = (raw['id'] as List).cast<int>();
} else if (raw is Map && raw['nfca'] is Map && raw['nfca']['identifier'] is List) {
idBytes = (raw['nfca']['identifier'] as List).cast<int>();
} else if (raw is Map && raw['mifareclassic'] is Map && raw['mifareclassic']['identifier'] is List) {
idBytes = (raw['mifareclassic']['identifier'] as List).cast<int>();
}
if (idBytes != null) {
final uid = idBytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join().toUpperCase();
result.uid = uid;
result.data['UID'] = uid;
}
final ndef = Ndef.from(tag);
if (ndef != null) {
result.tagType = 'NDEF';
if (ndef.cachedMessage != null) {
final message = ndef.cachedMessage!;
result.data['ndef'] = {
'records': message.records.map((record) {
return {
'type': record.type,
'payload': record.payload,
'tnf': record.typeNameFormat.index,
'id': record.identifier,
};
}).toList(),
};
} else {
result.data['ndef'] = {'records': <Map<String, dynamic>>[]};
}
} else {
result.tagType = 'Non-NDEF';
}
} catch (e) {
result.data['error'] = e.toString();
}
return result;
}
Code language: PHP (php)
Code Explanation:
- NfcManager.instance.startSession(…): This function starts an NFC reader/writer Flutter session.
- onDiscovered: (NfcTag tag) async { … }: This callback is executed when an NFC tag is discovered. The NfcTag object contains the data.
- Ndef.from(tag): We get the NDEF (NFC Data Exchange Format) specific data from the tag.
- NfcManager.instance.stopSession(): It’s important to stop the session after processing the tag to release system resources.
- extractNdefText(…): Parses the first NDEF Text Record (type ‘T’) and decodes the text according to the NFC Forum Text RTD specification.
- UID extraction: Reads the tag’s unique identifier from the raw tag map and formats it as an uppercase hex string.
Handling Permissions

For iOS, you need to add the “Near Field Communication Tag Reading” capability in Xcode. You also need to add the following key to your Info.plist file:
info.plist
<key>NFCReaderUsageDescription</key>
<string>NFC is used to read tags.</string>
Code language: HTML, XML (xml)
For Android, add the following permission to your AndroidManifest.xml file:
<uses-permission android:name="android.permission.NFC" />
Code language: HTML, XML (xml)
Following platform-specific practices ensures smooth integration, which is why many enterprises rely on an experienced mobile app development company to guide them through multi-platform NFC implementation.
Writing to NFC Tags
Writing data to NFC tags in Flutter is also straightforward.
Code Snippet:
Future<void> _startNFCWrite() async {
final text = _writeTextController.text.trim();
if (text.isEmpty) {
setState(() {
_status = 'Please enter text to write to the NFC card';
});
return;
}
try {
setState(() {
_isScanning = true;
_status = _isIOS
? 'Hold a writable NFC card near the top of your iPhone...'
: 'Hold a writable NFC card near the back of your device...';
});
await NfcManager.instance.startSession(
pollingOptions: {NfcPollingOption.iso14443},
onDiscovered: (NfcTag tag) async {
try {
final ndef = Ndef.from(tag);
if (ndef == null) {
throw Exception('This tag does not support NDEF');
}
if (!ndef.isWritable) {
throw Exception('This tag is not writable');
}
// Build an NDEF Text Record (type 'T') using NFC Forum Text RTD
final languageCode = 'en';
final langBytes = utf8.encode(languageCode);
final textBytes = utf8.encode(text);
final statusByte = langBytes.length & 0x3F; // bit7=0 (UTF-8), lower 6 = lang length
final payload = Uint8List.fromList([
statusByte,
...langBytes,
...textBytes,
]);
final textRecord = NdefRecord(
typeNameFormat: TypeNameFormat.wellKnown,
type: Uint8List.fromList('T'.codeUnits),
identifier: Uint8List(0),
payload: payload,
);
final message = NdefMessage(records: [textRecord]);
await ndef.write(message: message);
setState(() {
_status = 'Data written successfully to NFC card!';
});
} catch (e) {
setState(() {
_status = 'Error writing to card: $e';
});
} finally {
await NfcManager.instance.stopSession();
setState(() => _isScanning = false);
}
},
);
} catch (e) {
setState(() {
_isScanning = false;
_status = 'Error starting NFC write: $e';
});
}
}
Code language: PHP (php)
Code Breakdown:
- NfcManager.instance.startSession(…): This function starts an NFC write session in Flutter for writing.
- onDiscovered: (NfcTag tag) async { … }: Triggered when a tag is detected and ready for write operations.
- Ndef.from(tag): Retrieves the NDEF interface for the detected tag.
- ndef.isWritable: Ensures the tag supports writing before proceeding.
- NdefMessage(…) + NdefRecord(…): We construct an NFC Forum Text record (type ‘T’) with a proper status byte, language code, and UTF-8 text payload.
- ndef.write(message: message): Writes the NDEF message to the tag.
- NfcManager.instance.stopSession(): We stop the session after the write is complete or if an error occurs.
User Interface (UI) Design

A simple, intuitive Flutter NFC reader writer UI improves the experience.
UI Code Snippet:
Dart
@override
void initState() {
super.initState();
_checkNfcAvailability();
}
@override
void dispose() {
_writeTextController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('NFC Reader/Writer')),
body: Center(
child: isNfcAvailable
? Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Status: $_status'),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _isScanning ? null : () => startRead(context),
child: const Text('Read NFC Tag'),
),
const SizedBox(height: 24),
TextField(
controller: _writeTextController,
decoration: const InputDecoration(
labelText: 'Text to write',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
ElevatedButton(
onPressed: _isScanning ? null : _startNFCWrite,
child: const Text('Write to NFC Tag'),
),
],
),
)
: const Text('NFC not available in this device'),
),
);}
Code language: PHP (php)
Code Explanation:
- _checkNfcAvailability(): Checks if the device supports NFC and updates the UI.
- ElevatedButton (Read): Starts an NFC read session and listens for tag discovery.
- TextField + ElevatedButton (Write): Accepts user input and writes it as an NDEF Text record to a writable tag.
- Disabled buttons while scanning: Prevents multiple sessions and improves user experience.
If you plan to extend your NFC-enabled app with networking, you can check out our Flutter Dio HTTP client tutorial for efficient API integration.
Testing the Application
- Connect your NFC-enabled device to your computer.
- Run the app using flutter run.
- The UI will tell you if NFC is available. If it is, tap the “Read NFC Tag” button and bring an NFC tag close to your device’s NFC antenna (usually on the back).
- To write, enter text, tap the “Write to NFC Tag” button, and bring a writable NFC tag near the device.
For apps with heavy processing in the background, you can also explore Flutter isolates background processing to improve responsiveness.
Conclusion
The nfc_manager Flutter package makes it easy to add solid NFC reader and writer functionality to your Flutter apps. We have established the flow of NFC checking for NFC availability, reading NFC tags, writing NDEF records, and handling potential errors. There are more advanced features to explore, such as handling different NDEF record types.
If you’d like to dive deeper into implementation or access sample code, check out the official GitHub repository.

Author's Bio

With over 5+ years of experience in developing innovative applications across Android, iOS, Web, Windows, and macOS platforms, I excel in Dart, Java, and Kotlin. My expertise spans the entire development lifecycle, from requirement analysis and technical documentation to coding, testing, and implementation. Skilled in design patterns like GetX, Provider, Bloc, SQLite, MVC, MVVM, and Singleton, I am adept at translating business needs into robust technical solutions. Recognized for my collaborative approach and award-winning performance, I am dedicated to transforming visionary concepts into impactful results.