Cordova NFC Plugin working but raises no event - android

I am using the phonegap-nfc plugin to read NFC tags in my cordova app. I also included the uses-permission tags in the AndroidManifest.xml.
My Typescript-Code is:
module myapp.nfcRead {
export class NfcCtrl {
private status:string;
constructor(private $cordovaNfc:any, private $cordovaNfcUtil:any) {
this.status = "nfc";
}
public readNFC() {
console.log("trying to find nfc");
this.$cordovaNfc.then((nfcInstance:any) => {
nfcInstance.addNdefListener((nfcEvent:any) => {
this.status = "NFC Detected";
})
.then(
//Success callback
(event:any) => {
this.status = "Reading NFC";
console.log("Reading NFC");
},
//Fail callback
(err:any) => {
this.status = "error";
console.log("error");
});
});
this.$cordovaNfcUtil.then((nfcUtil:any) => {
this.status = nfcUtil.bytesToString("some bytes");
});
}
}
NfcCtrl.$inject = ['$cordovaNfc', '$cordovaNfcUtil'];
angular.module('myapp.nfcRead', ['ngCordova.plugins.nfc']).controller('NfcCtrl', NfcCtrl);}
If i start the App in the browser and i call the method "readNFC()" the status remains "nfc". If i deploy the app on my android phone and i call the "readNFC()"-function and NFC is disabled, the status is "error". After activating NFC and calling the function again, it says "Reading NFC". Now i want to read NFC tags, but the status doesn't change. (no event is raised)
I downloaded an app from the Google Play Store to see if tags are discovered:
(app: NFC Tools)
NFC Tools output
Do you have any tip for me?
Thank you

Your code is using nfc.addNdefListener which will only find tags that contain NDEF messages.
The screenshot from NFC Tools says your tag is NDEF Formatable, so I don't think it contains an NDEF message.
You can change your code to use nfc.addTagDiscoveredListener or nfc.addNdefFormatableListener. It's fine to have multiple listeners. Android will call the most specific listener for the tag that is scanned.
Alternately, you can use NFCTools to write an NDEF message onto the tag. Then try reading it with your exiting code.

Related

The default Android NFC reader overrides the reader in my FLUTTER app

I have an application where I am supposed to scan NFC tags and Call different functions depending on the tag result, but every time I scan an NFC on my phone the default Android reader pops up and the app doesnt recognize it, is there a way I can override the default reader and let the application scan the NFC instead?
ValueNotifier<dynamic> result = ValueNotifier(null);
void _tagRead() {
NfcManager.instance.startSession(
onDiscovered: (NfcTag tag) async {
result.value = tag.data;
if(result.value.toString().toLowerCase()=="main " || result.value.toString().toLowerCase()=="it" || result.value.toString().toLowerCase()=="eng"){
addRecord(result.value.toString());
}
else{
registerAttendance(result.value.toString());
}
NfcManager.instance.stopSession();
});
}

Flutter Deep Linking

According to the Flutter's official deep linking page, we do not require any plugin or native Android/iOS code for handling deep links.
But it doesn't really tell us how we can get the data from that link. I'm talking from coding perspective. Sure, they have written in there that:
But this does not tell me where should I write what code to actually get the complete link. I've looked for examples/tutorials but I'm unable to find anything that is not using a plugin for handling deep linking.
Right now, all I've done is add <intent-filter> tags in AndroidManifest.xml file and on clicking the link, my app has started to show up. But I don't know how to extract data from that link.
Is there anyone who can guide me here? Thanks in advance.
You need platform specific code to handle deep linking. If you follow link mention in documention, you will find complete example.
private val CHANNEL = "poc.deeplink.flutter.dev/channel"
private var startString: String? = null
override fun configureFlutterEngine(#NonNull flutterEngine:FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
MethodChannel(flutterEngine.dartExecutor, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "initialLink") {
if (startString != null) {
result.success(startString)
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val intent = getIntent()
startString = intent.data?.toString()
}
Flutter Code:
class DeepLinkBloc extends Bloc {
//Event Channel creation
static const stream = const
EventChannel('poc.deeplink.flutter.dev/events');
//Method channel creation
static const platform = const
MethodChannel('poc.deeplink.flutter.dev/channel');
StreamController<String> _stateController = StreamController();
Stream<String> get state => _stateController.stream;
Sink<String> get stateSink => _stateController.sink;
//Adding the listener into contructor
DeepLinkBloc() {
//Checking application start by deep link
startUri().then(_onRedirected);
//Checking broadcast stream, if deep link was clicked in opened appication
stream.receiveBroadcastStream().listen((d) => _onRedirected(d));
}
_onRedirected(String uri) {
// Here can be any uri analysis, checking tokens etc, if it’s necessary
// Throw deep link URI into the BloC's stream
stateSink.add(uri);
}
#override
void dispose() {
_stateController.close();
}
Future<String> startUri() async {
try {
return platform.invokeMethod('initialLink');
} on PlatformException catch (e) {
return "Failed to Invoke: '${e.message}'.";
}
}
}
Follow this link for more detail.
https://medium.com/flutter-community/deep-links-and-flutter-applications-how-to-handle-them-properly-8c9865af9283
The Flutter way to do that, assuming you've already made the steps in the guide you posted, is to create a onGenerateRoute and/or onGenerateInitialRoutes handlers in your MaterialApp so that these handlers deals with the routes passed or pushed by the framework according to the described behaviors. You can even create an expected named route coming from a deeplink on the routes property of MaterialApp, even though I believe the dynamic generation of routes is more appropriate due to the dynamic nature of deeplinking, specially if you're dealing with "authentication needed content" inside your app.
Or, if you don't want to pass trough the platform specific code, you could use firebase dynamic links. That would allow to easily listen to links coming from both platforms and you also get the advantage that your link would bring up the store listing page if the user doesn't have the app installed.
I've written a full example here: https://gbaccetta.medium.com/flutter-deep-linking-with-firebase-dynamic-links-and-bloc-architecture-660f0517fbc2
Do for android as said in the web page: "Add a metadata tag and intent filter to AndroidManifest.xml inside the tag with the ".MainActivity" name". Do what required for ios too.
Then use onGenerateRoute in the usual way in MaterialApp, don't use "routes:". For example:
onGenerateRoute: (settings) {
print("settings.name " + settings.name.toString());
if (settings.name == '/') return MaterialPageRoute(builder: (_) => ScreenStart());
return MaterialPageRoute(builder: (_) => ScreenUnknown());
},
Then to simulate I did:
cd /Users/Utente/AppData/Local/Android/Sdk/platform-tools
adb shell
am start -W -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "http://theaddressichoosed.com/helloworld?byebye"
And
print("settings.name " + settings.name.toString());
printed
settings.name /helloworld?byebye
After spending some time on this, here's my take using the Navigator 2 API. It also shows how to perform query and path arguments parsing. Hope it will save someone the time I spent researching this.
Obviously you also need to edit your platform-specific build files (such as AndroidManifest.xml for Android) as shown in the Flutter Deep Linking page.
A special note for Android 12 and above: you'll also need to securely approve the app's domain in the Google Play Developer Console for deep linking to work.
class App extends StatelessWidget {
const App({super.key});
#override
Widget build(BuildContext context) => MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomeScaffold(),
'/route1': (context) => const RouteOneScaffold(),
'/route2': (context) => const RouteTwoScaffold(),
// Other routes which don't need any sort of query parsing
},
onGenerateRoute: (settings) {
// This is executed to determine which route to follow if no adequate entry is found in the `routes` array above.
// Here we can parse path and query parameters as we like.
final fullRoute = settings.name;
if (fullRoute == null) {
return null;
}
final routeData = Uri.tryParse(fullRoute);
if (routeData == null) {
return null;
}
final pathParameters = routeData.pathSegments;
final queryParameters = routeData.queryParameters;
// Here you can write your route handling logic
return MaterialPageRoute(builder: (context) => RouteThreeScaffold(pathParameters,queryParameters));
},
);
}

Enable Ignore Battery Optimization for Ionic app

I want to Ignore Battery Optimization in our Ionic App, I have read about https://developer.android.com/reference/android/Manifest.permission#REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
For this I have added cordova-plugin-power-optimization Plugin that will add <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/> in AndroidManifest.xml,
Now I want to send Intent https://developer.android.com/reference/android/provider/Settings#ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS for the user to go to settings screen where he can exclude battery optimization,
I am not sure how to achieve this with Ionic WebIntent plugin, which we generally use to send/receive Intents
So far I am trying this code but not sure how to set Options and startActivity
Can anyone help please, I am using Ionic 4 with Cordova 7.1.4
this.androidPermissions.checkPermission(this.androidPermissions.PERMISSION.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
.then((result) => {
alert('Has permission?'+result.hasPermission)
this.androidPermissions.requestPermission(this.androidPermissions.PERMISSION.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
.then((data)=>{
alert('request permission then' + data.hasPermission);
//what should go here in options and startActivity
const openOptions = {
action: this.webIntent.ACTION_VIEW,
url: packageFileUrl,
type: 'application/vnd.android.package-archive'
};
this.webIntent.startActivity(openOptions).then((data1)=>{alert('data:'+data1)});
})
.catch((data)=>{
alert('request permission catch'+data.hasPermission)
})
},
);```
I am able to send Intent through WebIntent, Its just I need to use Constant value
"android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" in Action part of Intent Options, This constant values are mentioned on
https://developer.android.com/reference/android/provider/Settings#ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
Also for url, I have used getPackageName() method of AppVersion Plugin and pass this in url section of Intent Options,Here appName contains package Name and getPackageName() method is called from constructor.
Also Type is not needed here.
private async IntentIgnoreBatteryOptimization(resolve, reject) {
const openOptions = {
action:'android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS',
url: 'package:'+this.appName
};
try{
resolve(await this.webIntent.startActivity(openOptions));
}
catch(err){
reject('Permission denied'+err);
}
}
async getPackageName(){
if (this.device.isVirtual !== null) {
await this.appVersion.getPackageName().then((result)=>{
this.appName = result;
})
}
}
I think we can achieve this scenario by using the ionic Cordova native settings, in which you can pass battery_optimization and the native device will lead you to the battery optimization area.

A phone call notification simulation using ionic framework

I want to send a push notification from server to an ionic client and show this notification in client like a phone call (mobile device should play a sound and show 'Accept' or 'Reject' buttons with caller information). It should work if mobile app is not running or in background, that's why I decided to use FCM messages.
this.storage.get('firebase_token').then((token) => {
console.log('Orders get firebase token and call register. Token: ' + token);
this.agentService.registerPushNotifications(token, () => {
this.firebase.onNotificationOpen().subscribe((notification) => {
// How to open the app and show the page with a ringtone ??
});
});
});
How can I open the app and show the call page with a ringtone in incoming push notification? Or maybe there is a better way for this kind of feature.
What you are asking for (the same format like a phone call) isn't possible with Ionic. You can however redirect the user to a view inside the application where you ask him to take action.
Take the following example for push notification. In app.components.ts I initialize this function when the platform is ready
initializePushNotifications() {
let pushObject = this.push.init({
android: {
senderID: 'Your ID Here',
icon: 'logo'
},
ios: {
alert: true,
badge: false,
sound: true
},
windows: {}
});
if (!pushObject['error']) {
pushObject.on('registration').subscribe((data: RegistrationEventResponse) => {
// Whatever you want to do
}, err => {
console.log('Couldnt register:', err);
})
pushObject.on('notification').subscribe((data: any) => {
let self = this;
// When the user click the push notification
if (!data.additionalData.foreground) {
switch (data.additionalData.entity_type) {
case 'takeAction':
this.openView(data.additionalData.user_name, data.additionalData.id);
break;
......
}
}
});
pushObject.on('error').subscribe((e: any) => {
console.log(e.message);
});
} else {
console.error(pushObject);
}
}
See, in the pushed message we add an object under the key additionalData where you can pass whatever you want. You can pass something like entity_type with the value takeAction. When the user click it, you can open a new view and pass additional parameters like the name of the user and the id of the entity or whatever.
On this screen you can open an alert asking the user to click yes or no and based on his input you fire the correct request.
Note
I know this is different from what you were asking for but your request cannot be fulfilled using Ionic.

Push Notification use payload to open correct page

First off I am using Xamarin Forms for a WP8, iOS and Android app.
Goal:
I want to go to a specific page when the toast is clicked depending
upon the payload information of the toast notification.
I have push notifications using Azure Notification Hubs all setup and working well. I use MVVMLight and their dependency injection to setup push notifications specifically for each platform.
Each payload needs to be sent a little different due to the different formats required. With each you will notice I want to send a SignalId in the payload to perform a different action as required on the receiving device from regular push notifications.
Android
{
"data" : {
"msg" : "message in here",
"signalId" : "id-in-here",
},
}
iOS
{
"aps" : { "alert" : "message in here" },
"signalId" : "id-in-here"
}
Windows Phone 8
<?xml version="1.0" encoding="utf-8"?>
<wp:Notification xmlns:wp="WPNotification">
<wp:Toast>
<wp:Text1>category</wp:Text1>
<wp:Text2>message in here</wp:Text2>
<wp:Param>?signalId=id-in-here</wp:Param>
</wp:Toast>
</wp:Notification>
.
Question:
How do I get this information in a Xamarin Forms app and redirect to
the appropriate page when the application is reactivated because the
user clicked on the toast notification?
I want to get the payload information when the app loads, then say, yes this contains a SignalId, lets redirect to this page.
At the moment all it does it show the application when a toast notification is clicked. Must I do it specific to the app, or is there a Xamarin Forms way?
Any help appreciated even if you only know how to do it for one platform, I can probably work my way around the other platforms from there.
I have found the way to do it for all platforms. Windows has been tested, Android and iOS haven't.
Windows and iOS work on a show toast notification if the app is in the background, or let your code deal with it if the app is in the foreground. Android shows the toast regardless of application status.
With Windows Phone 8 I need to go to the MainPage.xaml.cs and add in this override.
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (this.NavigationContext.QueryString.ContainsKey("signalId"))
{
var signalId = this.NavigationContext.QueryString["signalId"];
var id = Guid.Empty;
if (signalId != null
&& Guid.TryParse(signalId, out id)
&& id != Guid.Empty)
{
this.NavigationContext.QueryString.Clear();
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
// Do my navigation to a new page
});
}
}
}
For Android in the GcmService
protected override void OnMessage(Context context, Intent intent)
{
Log.Info(Tag, "GCM Message Received!");
var message = intent.Extras.Get("msg").ToString();
var signalId = Guid.Empty;
if (intent.Extras.ContainsKey("signalId"))
{
signalId = new Guid(intent.Extras.Get("signalId").ToString());
}
// Show notification as usual
CreateNotification("", message, signalId);
}
Then in the CreateNotification function put some extra information in the Intent.
var uiIntent = new Intent(this, typeof(MainActivity));
if (signalId != Guid.Empty)
{
uiIntent.PutExtra("SignalId", signalId.ToString());
}
Then in the MainActivity.cs override this function
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
if (data.HasExtra("SignalId"))
{
Guid signalId = new Guid(data.GetStringExtra("SignalId"));
if (signalId != Guid.Empty)
{
data.RemoveExtra("SignalId");
// Do you navigation
}
}
}
In iOS you will notice I have enhanced the default ProcessNotification()
void ProcessNotification(NSDictionary options, bool fromFinishedLaunching)
{
// Check to see if the dictionary has the aps key. This is the notification payload you would have sent
if (null != options && options.ContainsKey(new NSString("aps")))
{
//Get the aps dictionary
var aps = options.ObjectForKey(new NSString("aps")) as NSDictionary;
var alert = string.Empty;
//Extract the alert text
// NOTE: If you're using the simple alert by just specifying
// " aps:{alert:"alert msg here"} " this will work fine.
// But if you're using a complex alert with Localization keys, etc.,
// your "alert" object from the aps dictionary will be another NSDictionary.
// Basically the json gets dumped right into a NSDictionary,
// so keep that in mind.
if (aps.ContainsKey(new NSString("alert")))
alert = ((NSString) aps[new NSString("alert")]).ToString();
// If this came from the ReceivedRemoteNotification while the app was running,
// we of course need to manually process things like the sound, badge, and alert.
if (!fromFinishedLaunching)
{
//Manually show an alert
if (!string.IsNullOrEmpty(alert))
{
var signalId = new Guid(options.ObjectForKey(new NSString("signalId")) as NSString);
// Show my own toast with the signalId
}
}
}
}
Then in the FinishedLaunching function check if there is any payload
// Check if any payload from the push notification
if (options.ContainsKey("signalId"))
{
var signalId = new Guid(options.ObjectForKey(new NSString("signalId")) as NSString);
// Do the navigation here
}

Categories

Resources