I'm trying to add Biometrics for IOS Devices but I keep on getting this error:
The method 'authenticateWithBiometrics' isn't defined for the type 'LocalAuthentication'.
This is the code for my api file:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_local_auth_invisible/auth_strings.dart';
import 'package:local_auth/local_auth.dart';
class LocalAuthApi {
static final _auth = LocalAuthentication();
static Future<bool> hasBiometrics() async {
try {
return await _auth.canCheckBiometrics;
} on PlatformException catch (e) {
return false;
}
}
static Future<bool> authenticate() async {
final isAvailable = await hasBiometrics();
if (!isAvailable) return false;
try {
return await _auth.authenticateWithBiometrics(
androidAuthStrings: const AndroidAuthMessages(
signInTitle: 'Face ID Required',
),
localizedReason: 'Scan Face to Authenticate',
useErrorDialogs: false,
stickyAuth: false,
);
} on PlatformException catch (e) {
return false;
}
}
}
The documentation of package: https://pub.dev/packages/local_auth/example
Just change _auth.authenticateWithBiometrics to _auth.authenticate it must be work. Then your code:
static Future<bool> authenticate() async {
final isAvailable = await hasBiometrics();
if (!isAvailable) return false;
try {
return await _auth.authenticate(
androidAuthStrings: const AndroidAuthMessages(
signInTitle: 'Face ID Required',
),
localizedReason: 'Scan Face to Authenticate',
options: const AuthenticationOptions(
useErrorDialogs: true,
stickyAuth: true,
),
);
} on PlatformException catch (e) {
return false;
}
}
Related
import 'dart:developer';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../export.dart';
class FirebaseNotificationManager {
FirebaseNotificationManager._privateConstructor();
static final FirebaseNotificationManager _instance = FirebaseNotificationManager._privateConstructor();
factory FirebaseNotificationManager() {
return _instance;
}
init() async {
// add firebase notification permission
FirebaseMessaging messaging = FirebaseMessaging.instance;
NotificationSettings settings = await messaging.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: true,
provisional: false,
sound: true,
);
if (await Permission.notification.request().isGranted) {
try {
// Either the permission was already granted before or the user just granted it.
FirebaseMessaging.onBackgroundMessage(_messageHandler);
_firebaseMessagingListener();
String? deviceToken = await FirebaseMessaging.instance.getToken();
logger.i(deviceToken);
} catch (e) {
logger.i(e);
}
}
}
/// must call it from view after getContext is initialized to show dialog message
checkAndroid() async {
if (!(await Permission.notification.request().isGranted) && GetPlatform.isAndroid) {
showOptionsDialog(
text: 'إذا كنت ترغب في تلقي الاشعارات ،برجاء اعطاء إذن الاشعارات في الإعدادات وإعادة تشغيل التطبيق',
yesFunction: (context) async {
openAppSettings();
});
}
}
// execute if app in background
Future<void> _messageHandler(RemoteMessage message) async {
// Data notificationMessage = Data.fromJson(message.data);
log('notification from background : ${message.toMap()}');
}
// execute if app in foreground
void _firebaseMessagingListener() {
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
logger.i('Got a message whilst in the foreground!');
logger.i('Message data: ${message.data}');
if (message.notification != null) {
logger.i('Message also contained a notification: ${message.notification!.toMap()}');
logger.i('Message also contained a notification: ${message.toMap()}');
// that means new message
try {
Get.snackbar(message.notification!.title.toString(), message.notification!.body.toString(),
duration: Duration(seconds: 6),
backgroundColor: Theme.of(Get.context!).cardColor,
barBlur: 10,
margin: EdgeInsets.all(10),
padding: EdgeInsets.all(8));
} catch (e) {
logger.i(e);
}
}
});
}
}
As per the documentation, you need to put the onBackgroundMessage function outside of a class, at the top of your file as a top-level function. See Firebase messaging example for implementation.
source
file became:
import 'dart:developer';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../export.dart';
init() async {
// add firebase notification permission
FirebaseMessaging messaging = FirebaseMessaging.instance;
NotificationSettings settings = await messaging.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: true,
provisional: false,
sound: true,
);
if (await Permission.notification.request().isGranted) {
try {
// Either the permission was already granted before or the user just granted it.
FirebaseMessaging.onBackgroundMessage(_messageHandler);
_firebaseMessagingListener();
String? deviceToken = await FirebaseMessaging.instance.getToken();
logger.i(deviceToken);
} catch (e) {
logger.i(e);
}
}
}
/// must call it from view after getContext is initialized to show dialog message
checkAndroid() async {
if (!(await Permission.notification.request().isGranted) && GetPlatform.isAndroid) {
showOptionsDialog(
text: 'إذا كنت ترغب في تلقي الاشعارات ،برجاء اعطاء إذن الاشعارات في الإعدادات وإعادة تشغيل التطبيق',
yesFunction: (context) async {
openAppSettings();
});
}
}
// execute if app in background
Future<void> _messageHandler(RemoteMessage message) async {
// Data notificationMessage = Data.fromJson(message.data);
log('notification from background : ${message.toMap()}');
}
// execute if app in foreground
void _firebaseMessagingListener() {
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
logger.i('Got a message whilst in the foreground!');
logger.i('Message data: ${message.data}');
if (message.notification != null) {
logger.i('Message also contained a notification: ${message.notification!.toMap()}');
logger.i('Message also contained a notification: ${message.toMap()}');
// that means new message
try {
Get.snackbar(message.notification!.title.toString(), message.notification!.body.toString(),
duration: Duration(seconds: 6),
backgroundColor: Theme.of(Get.context!).cardColor,
barBlur: 10,
margin: EdgeInsets.all(10),
padding: EdgeInsets.all(8));
} catch (e) {
logger.i(e);
}
}
});
}
my IOS Flutter App shows after receiving an firebase notification a red dot (5 unread messages) on my app icon. But when you click on the notification there are still 5 unread notifications. This problem is only on IOS not on Android.
The class notificationservice contains every function for fcm.
Do someone have a clue?
main.dart:
Future<void> newApp() async {
WidgetsFlutterBinding.ensureInitialized();
await AppChecker.checkAppStatus();
if (OckData.FCMcheckExecute) {
await Firebase.initializeApp();
firebaseNotificationService fs = firebaseNotificationService();
await FirebaseMessaging.instance.subscribeToTopic('news');
await FirebaseMessaging.instance.subscribeToTopic('tester14');
await fs.firebaseMain();
}
await FirebaseMessaging.instance
.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
NotificationSettings settings = await FirebaseMessaging.instance.requestPermission(
alert: true,
announcement: true,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: true,
sound: true,
);
runApp( const MyApp());
}
notificationservice.dart:
class firebaseNotificationService {
late FirebaseMessaging messaging;
firebaseNotificationService (){
if (OckData.FCMcheckExecute) {
messaging = FirebaseMessaging.instance;
}
}
Future<String?> getToken () async{
if (OckData.FCMcheckExecute) {
return await messaging.getToken(
vapidKey: OckData.firebase_webpush,
);
}
}
listenMessage () {
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
});
}
}
firebaseMain() async {
if (OckData.FCMcheckExecute) {
RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage();
// If the message also contains a data property with a "type" of "chat",
// navigate to a chat screen
if (initialMessage != null) {
OckData.initinalRout = '/post';
if(initialMessage.notification!.title == null){
OckData.postTitle = 'FEHLER: Nicht erkannt';
OckData.postId = '38';//initialMessage.data['body'];
}
else{
OckData.postTitle = initialMessage.notification!.title.toString();
OckData.postId = initialMessage.data['body'];
}
}
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingNewHandler);
FirebaseMessaging.instance
.getInitialMessage()
.then((value) => value != null ? _firebaseMessagingNewHandler : false);
FirebaseMessaging.onMessageOpenedApp.listen(_firebaseMessagingNewHandler);
FirebaseMessaging.onMessage.listen((RemoteMessage message) async{
return await _firebaseMessagingNewHandler (message);
});
}
}
}
void postselect (String postid, String betreff){
Abos abo = Abos ();
NavKey.navigatorKey.currentState!.pushNamed('/post', arguments: [postid, betreff]).then(abo.onGoBack);
}
Future<void> _firebaseMessagingNewHandler(RemoteMessage message) async {
if (OckData.FCMcheckExecute) {
String title = '';
String id = '';
if(message.notification == null){
title = 'FEHLER: Nicht erkannt';
id = message.data['body'];
}
else{
print(message.data['body']);
title = message.notification!.title.toString();
id = message.data['body'];
}
OckData.initinalRout = '/initinalStart';
postselect(id, title);
}
}
Future<bool> checkInitinalMessage () async {
RemoteMessage? initinalMessage = await getInitialMessage();
if(initinalMessage!=null){
return true;
}
else{
return false;
}
}
Future<RemoteMessage?> getInitialMessage () async {
RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage();
return initialMessage;
}
I solved the problem myself: The red dot is called badge. And in my Server Script there was the argument badge = 5 by sending the notification (in the curl request).
'notification' => array(
'title' => $title,
'body' => $message,
'content_available' => "true",
'sound' => 'default',
'data' => $id,
'badge' => '5'
),
I have added Facebook login to my flutter project and its logging in successfully but the problem is, The user is not able to enter the home screen of the application and stays back on the login screen. It is responding as Http status error [500].
Below is the code for Facebook login/authentication:
void doFacebookSignIn() async{
print("FaceBook Clicked");
try {
final result =
await FacebookAuth.i.login(permissions: ['email']);
if (result.status == LoginStatus.success) {
final userData = await FacebookAuth.i.getUserData();
print(userData);
hitFacebookApi(result.accessToken.token);
await FacebookAuth.i.logOut();
if (result.status == LoginStatus.cancelled) {
ToastUtils.showCustomToast(context, "cancelled", Colors.white , MyColors.primaryColor);
}
if (result.status == LoginStatus.failed) {
ToastUtils.showCustomToast(context, result.message, Colors.white , MyColors.primaryColor);
}
}
} catch (error) {
print(error);
}
}
Code for entering from Login to Home screen:
void hitFacebookApi(String token) {
CommonApis().logInWithFB(
{"token": "$token"}, CommonUtils.getLanguage(context) == "english")
.then((value) async{
if (value is Map) {
String fullToken = "Bearer ${value['token']}";
ApiUtils.headerWithToken.update("Authorization",(value)=> fullToken);
await userData.save(fullToken, "client");
await userService.getProfile();
Navigator.pushAndRemoveUntil(context,PageTransition(type: PageTransitionType.fade, child: ClientMain()), (Route<dynamic> route) => false);
} else {
ToastUtils.showCustomToast(
context, value, Colors.white, MyColors.primaryColor);
print("the cause "+value);
}
});
}
Code for API method:
Future<dynamic> logInWithFB(dynamic data ,bool isEnglish) async{
try{
final response= await Dio().post("${ApiUtils.BaseApiUrl}/auth/social/facebook",data: data,options: Options(headers: ApiUtils.headerForRegister ));
if(response.statusCode==200){
return {
"token" : response.data['token']
};
}
else{
return isEnglish?response.data['error']['en']:response.data['error']['ar'];
}
}on DioError catch(e) {
if(e.response !=null) {
return e.message;
}
}
}
I have difficulty implementing the Email Link login with Firebase.
I send the email link using:
_firebaseAuth.sendSignInLinkToEmail(
email: email,
actionCodeSettings: ActionCodeSettings(
url: 'https://subdomain.example.com/user-auth', //<subdomain.example.com> = my real domain
handleCodeInApp: true,
androidInstallApp: true,
androidPackageName: 'com.example.app',
),
);
Email is sent and when clicking I open the link using the DynamicLink package:
void _handleDynamicLinks() {
FirebaseDynamicLinks.instance.onLink(onSuccess: _onSuccess);
}
Future<dynamic> _onSuccess(PendingDynamicLinkData data) async {
print('---onLink---');
// How to pass signIn link to `isSignInWithEmailLink` and `signInWithEmailLink` ???
// data.link returns `https://subdomain.example.com/user-auth` which is not the complete link
}
Every method I call on PendingDynamicLinkData data doesn't return the full dynamic link and isSignInWithEmailLink returns false!
Try this in your _handleDynamicLink function.
try {
FirebaseDynamicLinks.instance.onLink.listen((dynamicLink) {
final Uri? deepLink = dynamicLink.link;
if (deepLink != null) {
emailLinkService.handleLink(deepLink, _emailController.text);
FirebaseDynamicLinks.instance.onLink.listen((dynamicLink) {
final Uri? deepLink = dynamicLink.link;
emailLinkService.handleLink(deepLink!, _emailController.text);
}, onError: (e) async {
print(e.message);
});
}
}, onError: (e) async {
print(e.message);
});
final PendingDynamicLinkData? data =
await FirebaseDynamicLinks.instance.getInitialLink();
final Uri? deepLink = data?.link;
print('deepLink :: $deepLink');
} catch (e) {
// you can print this error as well
}
And check if your url is the same as here:
And also add the Dynamic link as your custom Authorised domain like this:
Here is the handleLink method:
class EmailLinkService {
final FirebaseAuth _auth = FirebaseAuth.instance;
Future<void> signInWithEmailAndLink(
{required String userEmail}) async {
var _userEmail = userEmail;
var acs = ActionCodeSettings(
url: Constants.firebaseProjectURL,
handleCodeInApp: true,
iOSBundleId: 'com.example....',
androidPackageName: 'com.example....',
try {
return await _auth
.sendSignInLinkToEmail(email: _userEmail, actionCodeSettings: acs);
} on FirebaseAuthException catch (e) {
}
void handleLink(Uri link, userEmail) async {
if (link != null) {
final UserCredential user =
await FirebaseAuth.instance.signInWithEmailLink(
email: userEmail,
emailLink: link.toString(),
);
} else {
print(" link is null");
}
}
}
I'm trying to use flutter_downloader package to download some files (images/pdf). There is a listView with ListTiles each containing a button to start downloading when clicked but this error occurs when scrolling the list view.
[ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: 'package:flutter_downloader/src/downloader.dart': Failed assertion: line 30 pos 12: '!_initialized': FlutterDownloader.initialize() must be called only once!
//my code is like this:
import 'dart:io';
import 'dart:isolate';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
class DownloadFile extends StatefulWidget {
DownloadFile({this.downloadUrl});
final String downloadUrl;
#override
_DownloadFileState createState() => _DownloadFileState();
}
class _DownloadFileState extends State<DownloadFile> {
String downloadId;
String _localPath;
ReceivePort _port = ReceivePort();
#override
void initState(){
super.initState();
_init();
}
Future<void> _init() async {
await FlutterDownloader.initialize();
IsolateNameServer.registerPortWithName(
_port.sendPort, 'downloader_send_port');
_port.listen((dynamic data) {
String id = data[0];
DownloadTaskStatus status = data[1];
int progress = data[2];
print("status: $status");
print("progress: $progress");
print("id == downloadId: ${id == downloadId}");
});
FlutterDownloader.registerCallback(downloadCallback);
_localPath = (await _findLocalPath()) + '/Download';
final savedDir = Directory(_localPath);
bool hasExisted = await savedDir.exists();
if (!hasExisted) {
savedDir.create();
}
}
static void downloadCallback(String id, DownloadTaskStatus status, int progress) {
print(
'Background Isolate Callback: task ($id) is in status ($status) and process ($progress)');
final SendPort send =
IsolateNameServer.lookupPortByName('downloader_send_port');
send.send([id, status, progress]);
}
Future<String> _findLocalPath() async {
final directory = await getExternalStorageDirectory();
return directory.path;
}
Future<bool> _checkPermission() async {
if (Theme.of(context).platform == TargetPlatform.android) {
PermissionStatus permission = await PermissionHandler()
.checkPermissionStatus(PermissionGroup.storage);
if (permission != PermissionStatus.granted) {
Map<PermissionGroup, PermissionStatus> permissions =
await PermissionHandler()
.requestPermissions([PermissionGroup.storage]);
if (permissions[PermissionGroup.storage] == PermissionStatus.granted) {
return true;
}
} else {
return true;
}
} else {
return true;
}
return false;
}
//----------------------------------------------------------------
#override
void dispose() {
super.dispose();
}
//---------------------------------------------------------------
#override
Widget build(BuildContext context) {
return FlatButton(
onPressed: () async {
if (await _checkPermission()) {
final taskId = await FlutterDownloader.enqueue(
url: widget.downloadUrl,
savedDir: _localPath,
showNotification:
true, // show download progress in status bar (for Android)
openFileFromNotification:
true, // click on notification to open downloaded file (for Android)
);
downloadId = taskId;
}
},
child: Text('Downloa File',style: TextStyle(color: Colors.teal),)
);
}
}
According to the Usage section in the flutter_downloader package and the error you are getting, you must call the FlutterDownloader.initialize not more than once.
You can do that in the main method of your application, just like so:
WidgetsFlutterBinding.ensureInitialized();
await FlutterDownloader.initialize();