I'm creating a Xamarin.Forms app where, upon placing an NFC card on the phone, the app should launch automatically and immediately read all tag data (ID and payload at least) in one cycle.
On some Android devices, the app launches as expected, but OnNewIntent in MainActivity is not triggered. And it also starts my app without reading any data. So somehow the tag is not detected.
On other Android phones, the app doesn't launch at all when placing the NFC card on the device.
I'm using the following code adapted from this project but have made significant changes.
My first question is that how can I ensure reliable app launching and data reading across Android devices? Are there any modifications needed to make OnNewIntent trigger consistently?
My second question is about achieving the same behavior on iOS. Is it possible to start the app and read NFC data in a single cycle on iOS with Xamarin.Forms, considering iOS NFC limitations.
NativeNFCAdapterService
using System;
using System.Linq;
using System.Threading.Tasks;
using NfcAdapter = Android.Nfc.NfcAdapter;
using Android.Content;
using Android.Nfc.Tech;
using Android.Nfc;
using Android.OS;
using NFCTestApp.Interfaces;
using NFCTestApp.Droid.Services;
using Xamarin.Essentials;
using Xamarin.Forms;
using NFCTestApp.Droid.Enums;
using System.IO;
[assembly: Dependency(typeof(NativeNFCAdapterService))]
namespace NFCTestApp.Droid.Services
{
class NativeNFCAdapterService : INfcAdapter
{
private readonly MainActivity mainActivity = (MainActivity)Platform.CurrentActivity;
private Lazy<NfcAdapter> lazynfcAdapter = new Lazy<NfcAdapter>(() => NfcAdapter.GetDefaultAdapter(Platform.CurrentActivity));
private NfcAdapter NfcAdapter => lazynfcAdapter.Value;
private PendingIntent pendingIntent;
private IntentFilter[] writeTagFilters;
private string[][] techList;
private ReaderCallback readerCallback;
public event Action<string> TagDiscovered;
public event Action<string> AllDataRead;
private NfcStatus NfcStatus => NfcAdapter == null ?
NfcStatus.Unavailable : NfcAdapter.IsEnabled ?
NfcStatus.Enabled : NfcStatus.Disabled;
public static Tag DetectedTag { get; set; }
public NativeNFCAdapterService()
{
Platform.ActivityStateChanged += Platform_ActivityStateChanged;
}
private void Platform_ActivityStateChanged(object sender, ActivityStateChangedEventArgs e)
{
switch (e.State)
{
case ActivityState.Resumed:
EnableForegroundDispatch();
break;
case ActivityState.Paused:
DisableForegroundDispatch();
break;
}
}
public void ConfigureNfcAdapter()
{
IntentFilter tagdiscovered = new IntentFilter(NfcAdapter.ActionTagDiscovered);
IntentFilter ndefDiscovered = new IntentFilter(NfcAdapter.ActionNdefDiscovered);
IntentFilter techDiscovered = new IntentFilter(NfcAdapter.ActionTechDiscovered);
tagdiscovered.AddCategory(Intent.CategoryDefault);
ndefDiscovered.AddCategory(Intent.CategoryDefault);
techDiscovered.AddCategory(Intent.CategoryDefault);
var intent = new Intent(mainActivity, mainActivity.Class).AddFlags(ActivityFlags.SingleTop);
pendingIntent = PendingIntent.GetActivity(mainActivity, 0, intent, PendingIntentFlags.Immutable);
techList = new string[][]
{
new string[] { nameof(NfcA) },
new string[] { nameof(NfcB) },
new string[] { nameof(NfcF) },
new string[] { nameof(NfcV) },
new string[] { nameof(IsoDep) },
new string[] { nameof(NdefFormatable) },
new string[] { nameof(MifareClassic) },
new string[] { nameof(MifareUltralight) },
};
readerCallback = new ReaderCallback();
readerCallback.OnTagDiscoveredEvent += HandleTagDiscovered;
writeTagFilters = new IntentFilter[] { tagdiscovered, ndefDiscovered, techDiscovered };
}
public void DisableForegroundDispatch()
{
NfcAdapter?.DisableForegroundDispatch(Platform.CurrentActivity);
NfcAdapter?.DisableReaderMode(Platform.CurrentActivity);
}
public void EnableForegroundDispatch()
{
if (pendingIntent == null || writeTagFilters == null || techList == null) { return; }
NfcAdapter?.EnableForegroundDispatch(Platform.CurrentActivity, pendingIntent, writeTagFilters, techList);
NfcAdapter?.EnableReaderMode(Platform.CurrentActivity, readerCallback, NfcReaderFlags.NfcA, null);
Task.Run(async () => await ReadAllTagInfoAsync());
}
public void UnconfigureNfcAdapter()
{
Platform.ActivityStateChanged -= Platform_ActivityStateChanged;
}
public void HandleTagDiscovered(string tagId)
{
TagDiscovered?.Invoke(tagId);
}
public async Task SendAsync(byte[] bytes)
{
Ndef ndef = null;
try
{
if (DetectedTag == null)
DetectedTag = await GetDetectedTag();
ndef = Ndef.Get(DetectedTag);
if (ndef == null) return;
if (!ndef.IsWritable)
{
await Application.Current.MainPage.DisplayAlert("Error", "Tag is readonly", "Ok");
return;
}
if (!ndef.IsConnected)
{
await ndef.ConnectAsync();
}
await WriteToTag(ndef, bytes);
}
catch (IOException)
{
await Application.Current.MainPage.DisplayAlert("Error", "Transmission error - possibly due to movement.", "Ok");
}
catch (Exception)
{
await Application.Current.MainPage.DisplayAlert("Error", "Request error", "Ok");
}
finally
{
if (ndef?.IsConnected == true) ndef.Close();
ndef = null;
DetectedTag = null;
}
}
private async Task<Tag> GetDetectedTag()
{
mainActivity.NfcTag = new TaskCompletionSource<Tag>();
readerCallback.NFCTag = new TaskCompletionSource<Tag>();
var tagDetectionTask = await Task.WhenAny(mainActivity.NfcTag.Task, readerCallback.NFCTag.Task);
return await tagDetectionTask;
}
private async Task WriteToTag(Ndef ndef, byte[] chunkedBytes)
{
var ndefRecord = new NdefRecord(NdefRecord.TnfWellKnown, NdefRecord.RtdText?.ToArray(), Array.Empty<byte>(), chunkedBytes);
NdefMessage message = new NdefMessage(new[] { ndefRecord });
ndef.WriteNdefMessage(message);
await Application.Current.MainPage.DisplayAlert("NFC", "Write Successful", "Ok");
}
public async Task<string> ReadAllTagInfoAsync()
{
if (DetectedTag == null)
{
DetectedTag = await GetDetectedTag();
}
var info = new System.Text.StringBuilder();
info.AppendLine("Tech List:");
foreach (var tech in DetectedTag.GetTechList())
{
info.AppendLine($"- {tech}");
}
Ndef ndef = Ndef.Get(DetectedTag);
if (ndef != null)
{
info.AppendLine("NDEF Supported: Yes");
info.AppendLine($"NDEF Type: {ndef.Type}");
info.AppendLine($"Is Writable: {ndef.IsWritable}");
info.AppendLine($"Max Size: {ndef.MaxSize} bytes");
var ndefMessage = ndef.CachedNdefMessage;
if (ndefMessage != null && ndefMessage.GetRecords().Any())
{
foreach (var ndefRecord in ndefMessage.GetRecords())
{
info.AppendLine($"Payload: {System.Text.Encoding.UTF8.GetString(ndefRecord.GetPayload())}");
}
}
}
else
{
info.AppendLine("NDEF Supported: No");
}
AllDataRead?.Invoke(info.ToString());
return info.ToString();
}
}
}
ReaderCallback.cs
public class ReaderCallback : Java.Lang.Object, NfcAdapter.IReaderCallback
{
public TaskCompletionSource<Tag> NFCTag { get; set; }
public event Action<string> OnTagDiscoveredEvent;
public void OnTagDiscovered(Tag tag)
{
var isSuccess = NFCTag?.TrySetResult(tag);
if (NFCTag == null || !isSuccess.Value)
NativeNFCAdapterService.DetectedTag = tag;
byte[] tagIdBytes = tag.GetId();
string tagId = BitConverter.ToString(tagIdBytes).Replace("-", "");
OnTagDiscoveredEvent?.Invoke(tagId);
}
}
MainActivity.cs
[MetaData(NfcAdapter.ActionTechDiscovered, Resource = "@xml/nfc_tech_filter")]
[IntentFilter(new[] { NfcAdapter.ActionTechDiscovered }, Categories = new[] { Intent.CategoryDefault }, DataMimeType = "text/plain")]
[IntentFilter(new[] { NfcAdapter.ActionNdefDiscovered }, Categories = new[] { Intent.CategoryDefault }, DataMimeType = "text/plain")]
[IntentFilter(new[] { NfcAdapter.ActionTagDiscovered }, Categories = new[] { Intent.CategoryDefault }, DataMimeType = "text/plain")]
[Activity(Label = "NFCTestApp", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
public TaskCompletionSource<Tag> NfcTag { get; set; }
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
}
protected override void OnNewIntent(Intent intent)
{
System.Diagnostics.Debug.WriteLine("It is here");
base.OnNewIntent(intent);
Xamarin.Forms.Device.BeginInvokeOnMainThread(async () =>
{
await Xamarin.Forms.Application.Current.MainPage.DisplayAlert("NFC Tag Discovered", "A1", "OK");
});
}
}
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="NFCTestApp.Droid">
<uses-permission android:name="android.permission.NFC" />
<application android:label="NFCTestApp" android:icon="@mipmap/icon">
<meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter" />
</application>
</manifest>
here is the card info
EDIT: nfc_tech_filter.xml
<?xml version="1.0" encoding="utf-8" ?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.IsoDep</tech>
<tech>android.nfc.tech.NfcA</tech>
<tech>android.nfc.tech.NfcB</tech>
<tech>android.nfc.tech.NfcF</tech>
<tech>android.nfc.tech.NfcV</tech>
<tech>android.nfc.tech.Ndef</tech>
<tech>android.nfc.tech.NdefFormatable</tech>
<tech>android.nfc.tech.MifareClassic</tech>
<tech>android.nfc.tech.MifareUltralight</tech>
</tech-list>
</resources>

NFC cardWhat kind off NFC card is that? NDEF? also that code looks familiar for some reason