Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/example-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ jobs:
variant: [debug, release]

steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '21'
Expand Down Expand Up @@ -49,7 +49,7 @@ jobs:
variant: [debug, release]

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: subosito/flutter-action@v2
with:
channel: 'stable'
Expand Down
4 changes: 1 addition & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,7 @@
* allow authentication of sector 0 (#157)
* fix data type check in `writeBlock` (#161)

## 3.6.0-rc.6

This is a release candidate for 3.6.0. Please test it and report any issues.
## 3.6.0

* Requires Dart 3.6+ and Flutter 3.24+
* Remove annoying dependency on `js` library, replace with `dart:js_interop`
Expand Down
6 changes: 3 additions & 3 deletions android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
org.gradle.jvmargs=-Xmx4096M
android.enableJetifier=true
AGPVersion=8.7.3
KotlinVersion=2.1.0
AGPVersion=8.13.0
KotlinVersion=2.2.10
2 changes: 1 addition & 1 deletion android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Fri Sep 08 22:01:14 CST 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,13 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
}
}
}
if (!nfcHandler.post(handledFn)) {
val looperThread = nfcHandler.looper?.thread
if (looperThread == null || !looperThread.isAlive) {
val thread = HandlerThread("FlutterNfcKit").apply { start() }
nfcHandler = Handler(thread.looper)
}
val posted = nfcHandler.post(handledFn)
if (!posted) {
result.error("500", "Failed to post job to NFC Handler thread.", null)
}
}
Expand Down
6 changes: 3 additions & 3 deletions example/android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
org.gradle.jvmargs=-Xmx1536M
org.gradle.jvmargs=-Xmx4096M

android.useAndroidX=true
android.enableJetifier=true
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false

AGPVersion=8.7.3
KotlinVersion=2.1.0
AGPVersion=8.13.0
KotlinVersion=2.2.10
2 changes: 1 addition & 1 deletion example/android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
2 changes: 1 addition & 1 deletion example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ packages:
path: ".."
relative: true
source: path
version: "3.6.0-rc.6"
version: "3.6.0"
flutter_test:
dependency: "direct dev"
description: flutter
Expand Down
134 changes: 134 additions & 0 deletions lib/flutter_nfc_kit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -523,4 +523,138 @@ class FlutterNfcKit {
static Future<Uint8List> readSector(int index) async {
return await _channel.invokeMethod('readSector', {'index': index});
}

/// Read a custom number of bytes from a MIFARE Classic tag (Android only).
///
/// Requires a valid NFC session and a MIFARE Classic tag.
///
/// Authentication is performed on each sector before reading.
/// Reading starts from [startSector] and [startBlockInSector], and continues
/// until [numBytes] are read.
///
/// Note: Trailer blocks (last block of each sector) are skipped for safety,
/// as they contain key and access bits.
///
/// Throws an [Exception] if the tag is not MIFARE Classic or if authentication fails.
///
/// Returns a [List<int>] of the requested bytes (up to [numBytes]).
Future<List<int>> readDataFromTag({
required NFCTag tag,
int startSector = 1,
int startBlockInSector = 1,
int numBytes = 36,
String keyA = 'FFFFFFFFFFFF',
String? keyB,
}) async {
if (tag.type != NFCTagType.mifare_classic) {
throw Exception(
'Only MIFARE Classic tags are supported for this operation.');
}

final List<int> result = [];
final int neededBlocks = (numBytes / 16).ceil();
int blocksRead = 0;

for (int currentSector = startSector;
currentSector < tag.mifareInfo!.sectorCount!;
currentSector++) {
if (blocksRead >= neededBlocks) {
break;
}

final bool authOk = await FlutterNfcKit.authenticateSector(currentSector,
keyA: keyA, keyB: keyB);
if (!authOk) {
throw Exception('Authentication failed for $currentSector sector.');
}

int blockStart = (currentSector == startSector) ? startBlockInSector : 0;
int blocksInSector = (currentSector < 32) ? 4 : 16;

for (int blockOffset = blockStart;
blockOffset < blocksInSector - 1;
blockOffset++) {
if (blocksRead >= neededBlocks) break;

final int blockIndex = (currentSector < 32)
? currentSector * 4 + blockOffset
: 32 * 4 + (currentSector - 32) * 16 + blockOffset;

final Uint8List block = await FlutterNfcKit.readBlock(blockIndex);
result.addAll(block);
blocksRead++;
}
}

return result.take(numBytes).toList();
}

/// Write a list of bytes to a MIFARE Classic tag (Android only).
///
/// Requires a valid NFC session and a MIFARE Classic tag.
///
/// Authentication is performed on each sector before writing.
/// Writing starts from [startSector] and [startBlockInSector], and continues
/// until all bytes in [data] are written.
///
/// Note: Trailer blocks (last block of each sector) are skipped to prevent
/// overwriting sector keys and access conditions.
///
/// Each block is 16 bytes. If a block is partially filled, it is padded with zeros.
///
/// Throws an [Exception] if the tag is not MIFARE Classic or if authentication fails.
Future<void> writeDataToTag({
required NFCTag tag,
required List<int> data,
int startSector = 1,
int startBlockInSector = 1,
String keyA = 'FFFFFFFFFFFF',
String? keyB,
}) async {
if (tag.type != NFCTagType.mifare_classic) {
throw Exception(
'Only MIFARE Classic tags are supported for this operation.');
}

final int neededBlocks = (data.length / 16).ceil();
int blocksWritten = 0;

for (int currentSector = startSector;
currentSector < tag.mifareInfo!.sectorCount!;
currentSector++) {
if (blocksWritten >= neededBlocks) {
break;
}

final bool authOk = await FlutterNfcKit.authenticateSector(currentSector,
keyA: keyA, keyB: keyB);
if (!authOk) {
throw Exception('Authentication failed for $currentSector sector.');
}

int blockStart = (currentSector == startSector) ? startBlockInSector : 0;
int blocksInSector = (currentSector < 32) ? 4 : 16;

for (int blockOffset = blockStart;
blockOffset < blocksInSector - 1;
blockOffset++) {
if (blocksWritten >= neededBlocks) {
break;
}

final int blockIndex = (currentSector < 32)
? currentSector * 4 + blockOffset
: 32 * 4 + (currentSector - 32) * 16 + blockOffset;

final List<int> blockData = List.filled(16, 0);
for (int i = 0; i < 16 && (blocksWritten * 16 + i) < data.length; i++) {
blockData[i] = data[blocksWritten * 16 + i];
}

await FlutterNfcKit.writeBlock(
blockIndex, Uint8List.fromList(blockData));
blocksWritten++;
}
}
}
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: flutter_nfc_kit
description: Provide NFC functionality on Android, iOS & Web, including reading metadata, read & write NDEF records, and transceive layer 3 & 4 data with NFC tags / cards
version: 3.6.0-rc.6
version: 3.6.0
homepage: "https://github.com/nfcim/flutter_nfc_kit"

environment:
Expand Down