Стек:
- Flutter
- Пакеты: nfc_manager, nfc_manager_ndef, ndef_record
- Устройства: Samsung SM-S918B (One UI/Android 14) или Samsung Galaxy
Note10+
- Встроенный режим чтения (Kotlin) с включением «залипания»
Код: Выделить всё
class MainActivity : FlutterActivity() {
private val CHANNEL = "de.mytagapp.app/nfc"
private var nfcAdapter: NfcAdapter? = null
private var readerModeSticky = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
nfcAdapter = NfcAdapter.getDefaultAdapter(this)
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"enableReaderMode" -> {
readerModeSticky = true
enableReaderModeInternal()
result.success(true)
}
"disableReaderMode" -> {
readerModeSticky = false
disableReaderModeInternal()
result.success(true)
}
else -> result.notImplemented()
}
}
}
override fun onResume() {
super.onResume()
if (readerModeSticky) enableReaderModeInternal()
}
override fun onPause() {
super.onPause()
// disable when not visible
disableReaderModeInternal()
}
private fun enableReaderModeInternal() {
val flags =
NfcAdapter.FLAG_READER_NFC_A or
// NfcAdapter.FLAG_READER_NFC_V // (tested on/off)
NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS or
NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK
// No-op callback; Flutter handles reads/writes.
nfcAdapter?.enableReaderMode(this, { /* no-op */ }, flags, null)
}
private fun disableReaderModeInternal() {
nfcAdapter?.disableReaderMode(this)
}
}
Activity имеет launchMode="singleTop".
- Глобальная «защита» на стороне Flutter
Код: Выделить всё
class NfcReaderGuard extends StatefulWidget {
final Widget child;
const NfcReaderGuard({super.key, required this.child});
@override
State createState() => _NfcReaderGuardState();
}
class _NfcReaderGuardState extends State with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
NfcReaderMode.enable(); // MethodChannel -> enableReaderMode
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) NfcReaderMode.enable();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
NfcReaderMode.disable();
super.dispose();
}
@override
Widget build(BuildContext context) => widget.child;
}
- Только сеансы, инициируемые пользователем
- Я вызываю NfcManager.instance.startSession(...) только после того, как пользователь нажимает
кнопку (чтение/запись), и останавливаю его сразу после этого. - Сначала я проверяю доступность.
- Записи имеют формат RTD_TEXT; нет записей URL-адресов, которые могли бы предложить системе
открыть браузер.
Несмотря на все вышеперечисленное, на устройстве Samsung система по-прежнему иногда автоматически обрабатывает тег (как будто мой ReaderMode не «владеет» им). Даже фокусировка на приложении (нажатие на пользовательский интерфейс) не всегда предотвращает появление автоматического чтения/обработчика. Чаще всего я наблюдаю это, когда приложение восстанавливает фокус или после короткого цикла выключения и включения экрана.
Вопросы к экспертам Samsung/NFC/Android/Flutter:
- Переопределяет ли Samsung поведение ReaderMode? На устройствах AOSP я бы
ожидал, что EnableReaderMode(...) с SKIP_NDEF_CHECK остановит
проверку NDEF платформы/автоматический пользовательский интерфейс. Есть ли особенности Samsung,
когда система по-прежнему отправляет сообщения своему собственному обработчику? - Я стреляю себе в ногу, смешивая EnableReaderMode(...)
и nfc_manager.startSession(...)? Может ли nfc_manager внутренне
переключить свою собственную диспетчеризацию чтения/переднего плана и случайно повторно включить
системные настройки по умолчанию на Samsung? - Рекомендуете ли вы вообще не использовать nfc_manager.startSession и
вместо этого выполнять обнаружение всех тегов в собственном onTagDiscovered, а затем пересылать
байты во Flutter через MethodChannel? - Будет ли EnableForegroundDispatch(...) более надежным на Samsung, чем
ReaderMode здесь? Если да, какую-либо минимальную настройку, которая сохраняет намерения в моей
Активности и надежно предотвращает захват
приложениями Samsung Wallet/Tag — не требуя от пользователей изменения настроек устройства? - Какие-либо специфичные для Samsung флаги или рекомендации, которые мне не хватает?
- Дополнительные флаги чтения?
- Проблемы со временем (например, повторное включение позже) обратный вызов жизненного цикла)?
– Известные варианты поведения пользовательского интерфейса, требующие другого подхода? - Другие обходные пути, которые вы использовали в рабочей среде? Мои ограничения:
- Мне нужно избегать всплывающих окон и автоматического открытия системы.
- Чтение/запись должны происходить только при явном действии пользователя.
- Я бы предпочел не просить пользователей отключать службы Samsung Wallet/Tag
в настройках, так как это отключит чтение тегов в целом (так показывают
мои тесты)
- Станьте полностью нативными для обнаружения: обработайте onTagDiscovered в Kotlin,
разберите там NDEF (или просто передайте необработанные данные/байты) во Flutter; вообще не
вызывайте nfc_manager.startSession. - Попробуйте Foreground Dispatch вместо ReaderMode и убедитесь, что фильтры
разрешают обработку URL-адресов. - Регулируйте время повторного включения в onResume() (например, публикуйте небольшую задержку) на
если Samsung делает что-то во время возобновления работы. - Дважды проверьте теги, используемые для тестирования (убедитесь, что они не содержат
URL/записей, которые агрессивно обрабатывает Samsung; у меня только RTD_TEXT
, но я проведу повторное тестирование с пустым/FORMAT).
Подробнее здесь: https://stackoverflow.com/questions/797 ... ng-flutter