Skip to content

Commit 843778e

Browse files
author
LEFEBVRE DIBON Emmanuel
committed
Handle tags on startup and resume with Android
1 parent fc23fe6 commit 843778e

File tree

3 files changed

+186
-12
lines changed

3 files changed

+186
-12
lines changed

android/src/main/kotlin/io/flutter/plugins/nfcmanager/NfcManagerPlugin.kt

Lines changed: 97 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.flutter.plugins.nfcmanager
22

33
import android.app.Activity
4+
import android.content.Intent
45
import android.nfc.NfcAdapter
56
import android.nfc.Tag
67
import android.nfc.tech.IsoDep
@@ -22,20 +23,72 @@ import io.flutter.plugin.common.MethodCall
2223
import io.flutter.plugin.common.MethodChannel
2324
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
2425
import io.flutter.plugin.common.MethodChannel.Result
26+
import io.flutter.plugin.common.EventChannel;
27+
import io.flutter.plugin.common.EventChannel.EventSink
28+
import io.flutter.plugin.common.EventChannel.StreamHandler
2529
import java.io.IOException
2630
import java.lang.Exception
2731
import java.util.*
2832

29-
class NfcManagerPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
33+
class NfcManagerPlugin: FlutterPlugin, MethodCallHandler, ActivityAware{
3034
private lateinit var channel : MethodChannel
3135
private lateinit var activity: Activity
3236
private lateinit var tags: MutableMap<String, Tag>
37+
38+
private var tagFromIntent: Tag? = null
39+
private var sinkTagDiscoveredEvents = ArrayList<EventSink>()
40+
41+
3342
private var adapter: NfcAdapter? = null
3443
private var connectedTech: TagTechnology? = null
3544

3645
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
37-
channel = MethodChannel(binding.binaryMessenger, "plugins.flutter.io/nfc_manager")
46+
val baseChannelName = "plugins.flutter.io/nfc_manager"
47+
48+
channel = MethodChannel(binding.binaryMessenger, baseChannelName)
3849
channel.setMethodCallHandler(this)
50+
51+
EventChannel(binding.binaryMessenger,
52+
baseChannelName + "/stream").setStreamHandler(
53+
object : StreamHandler {
54+
private lateinit var currentEvents : EventSink
55+
56+
override fun onListen(arguments: Any?, events: EventSink) {
57+
if (sinkTagDiscoveredEvents.isEmpty()) {
58+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
59+
events.error("unavailable", "Requires API level 19.", null)
60+
} else {
61+
val adapter = adapter ?: run {
62+
events.error("unavailable", "NFC is not available for device.", null)
63+
return
64+
}
65+
66+
var argMaps = arguments as HashMap<String,Any?>
67+
adapter.enableReaderMode(activity, NfcAdapter.ReaderCallback {
68+
activity.runOnUiThread { broadcastPreparedTag(it) }
69+
}, getFlags(argMaps["pollingOptions"] as List<String>), null)
70+
}
71+
}
72+
73+
currentEvents = events;
74+
sinkTagDiscoveredEvents.add(currentEvents);
75+
76+
tagFromIntent?.let {
77+
currentEvents.success(prepareTag(it))
78+
tagFromIntent = null
79+
}
80+
}
81+
82+
override fun onCancel(arguments: Any?) {
83+
sinkTagDiscoveredEvents.remove(currentEvents);
84+
85+
if (sinkTagDiscoveredEvents.isEmpty()) {
86+
adapter?.disableReaderMode(activity)
87+
}
88+
}
89+
}
90+
)
91+
3992
adapter = NfcAdapter.getDefaultAdapter(binding.applicationContext)
4093
tags = mutableMapOf()
4194
}
@@ -46,6 +99,24 @@ class NfcManagerPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
4699

47100
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
48101
activity = binding.activity
102+
103+
binding.addOnNewIntentListener(fun(intent: Intent?): Boolean {
104+
var tagProcessed = false;
105+
106+
if (intent != null){
107+
val tag = processIntent(intent)
108+
109+
110+
if (tag != null) {
111+
broadcastPreparedTag(tag)
112+
tagProcessed = true
113+
}
114+
}
115+
116+
return tagProcessed
117+
})
118+
119+
processIntent(activity.intent)
49120
}
50121

51122
override fun onDetachedFromActivity() {
@@ -104,11 +175,11 @@ class NfcManagerPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
104175
result.error("unavailable", "NFC is not available for device.", null)
105176
return
106177
}
107-
adapter.enableReaderMode(activity, {
108-
val handle = UUID.randomUUID().toString()
109-
tags[handle] = it
110-
activity.runOnUiThread { channel.invokeMethod("onDiscovered", getTagMap(it).toMutableMap().apply { put("handle", handle) }) }
178+
179+
adapter.enableReaderMode(activity, NfcAdapter.ReaderCallback {
180+
activity.runOnUiThread { channel.invokeMethod("onDiscovered", prepareTag(it)) }
111181
}, getFlags(call.argument<List<String>>("pollingOptions")!!), null)
182+
112183
result.success(null)
113184
}
114185
}
@@ -345,4 +416,24 @@ class NfcManagerPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
345416
connectedTech = tech
346417
}
347418
}
419+
420+
private fun processIntent(intent: Intent) : Tag? {
421+
tagFromIntent = intent?.getParcelableExtra(NfcAdapter.EXTRA_TAG)
422+
423+
return tagFromIntent
424+
}
425+
426+
private fun broadcastPreparedTag(tag: Tag) {
427+
sinkTagDiscoveredEvents.forEach {
428+
val preparedTag = prepareTag(tag)
429+
it.success(preparedTag)
430+
}
431+
}
432+
433+
private fun prepareTag(tag: Tag): MutableMap<String, Any?> {
434+
val handle = UUID.randomUUID().toString()
435+
tags[handle] = tag
436+
437+
return getTagMap(tag).toMutableMap().apply { put("handle", handle) }
438+
}
348439
}

lib/src/channel.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
import 'package:flutter/services.dart';
22

3-
const MethodChannel channel = MethodChannel('plugins.flutter.io/nfc_manager');
3+
const baseChannelName = 'plugins.flutter.io/nfc_manager';
4+
5+
const MethodChannel channel = MethodChannel(baseChannelName);
6+
const EventChannel eventChannel = EventChannel(baseChannelName + '/stream');

lib/src/nfc_manager/nfc_manager.dart

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:async';
2+
13
import 'package:flutter/services.dart';
24

35
import '../channel.dart';
@@ -9,6 +11,19 @@ typedef NfcTagCallback = Future<void> Function(NfcTag tag);
911
/// Signature for `NfcManager.startSession` onError callback.
1012
typedef NfcErrorCallback = Future<void> Function(NfcError error);
1113

14+
/// Handle the discovering nfc tag session
15+
class NfcSessionHandler {
16+
final StreamSubscription<NfcTag> _subscription;
17+
18+
const NfcSessionHandler._({required StreamSubscription<NfcTag> subscription})
19+
: _subscription = subscription;
20+
21+
/// Stop the current session
22+
Future<void> stop() {
23+
return _subscription.cancel();
24+
}
25+
}
26+
1227
/// The entry point for accessing the NFC session.
1328
class NfcManager {
1429
NfcManager._() {
@@ -30,6 +45,64 @@ class NfcManager {
3045
return channel.invokeMethod('Nfc#isAvailable').then((value) => value!);
3146
}
3247

48+
/// (Android only)
49+
/// Get the NFC on startup or resume of the application and start session
50+
///
51+
/// `pollingOptions` is used to specify the type of tags to be discovered. All types by default.
52+
///
53+
/// Think to stop session after discovering
54+
/// ```dart
55+
/// final session = NfcManager.instance.discover(onDiscovered: (tag) {
56+
/// // Do something with tag
57+
/// });
58+
///
59+
/// session.stop();
60+
/// ```
61+
///
62+
/// To prevent application from being restarted when a NFC intent resume
63+
/// the app, set `android:launchMode="singleTask"` on the main `activity` in
64+
/// the `AndroidManifest.xml`
65+
NfcSessionHandler discover({
66+
required NfcTagCallback onDiscovered,
67+
Set<NfcPollingOption>? pollingOptions,
68+
String? alertMessage,
69+
NfcErrorCallback? onError,
70+
}) {
71+
pollingOptions ??= NfcPollingOption.values.toSet();
72+
73+
// Cancellation handled by [NfcSessionManager.stop]
74+
// ignore: cancel_subscriptions
75+
final subscription = eventChannel
76+
.receiveBroadcastStream({
77+
'pollingOptions':
78+
pollingOptions.map((e) => $NfcPollingOptionTable[e]).toList(),
79+
})
80+
.handleError((error) {
81+
late NfcError nfcError;
82+
if (error is PlatformException && error.code == 'unavailable') {
83+
nfcError = NfcError(
84+
type: NfcErrorType.unavailable,
85+
message: error.message ?? 'Unknown',
86+
details: error.details);
87+
} else {
88+
nfcError = $GetNfcError(error);
89+
}
90+
91+
if (onError != null) {
92+
onError(nfcError);
93+
} else {
94+
// To be handled by the framework
95+
throw nfcError;
96+
}
97+
})
98+
.map((tagMap) => $GetNfcTag(Map.from(tagMap)))
99+
.listen((tag) {
100+
_handleOnDiscovered(tag, onDiscovered);
101+
}, cancelOnError: false);
102+
103+
return NfcSessionHandler._(subscription: subscription);
104+
}
105+
33106
/// Start the session and register callbacks for tag discovery.
34107
///
35108
/// This uses the NFCTagReaderSession (on iOS) or NfcAdapter#enableReaderMode (on Android).
@@ -88,7 +161,8 @@ class NfcManager {
88161
Future<void> _handleMethodCall(MethodCall call) async {
89162
switch (call.method) {
90163
case 'onDiscovered':
91-
_handleOnDiscovered(call);
164+
_handleOnDiscovered(
165+
$GetNfcTag(Map.from(call.arguments)), _onDiscovered);
92166
break;
93167
case 'onError':
94168
_handleOnError(call);
@@ -99,10 +173,13 @@ class NfcManager {
99173
}
100174

101175
// _handleOnDiscovered
102-
void _handleOnDiscovered(MethodCall call) async {
103-
final tag = $GetNfcTag(Map.from(call.arguments));
104-
await _onDiscovered?.call(tag);
105-
await _disposeTag(tag.handle);
176+
Future<void> _handleOnDiscovered(
177+
NfcTag tag, NfcTagCallback? onDiscovered) async {
178+
try {
179+
await onDiscovered?.call(tag);
180+
} finally {
181+
await _disposeTag(tag.handle);
182+
}
106183
}
107184

108185
// _handleOnError
@@ -188,6 +265,9 @@ enum NfcErrorType {
188265
/// The user canceled the session.
189266
userCanceled,
190267

268+
/// The NFC is unavailable (API, disabled, ...)
269+
unavailable,
270+
191271
/// The session failed because the unexpected error has occurred.
192272
unknown,
193273
}

0 commit comments

Comments
 (0)