Flutter: iOS doesn't ask for permissions - flutter

I have this code showing the Retry button:
TextButton(
onPressed: () async {
await _checkPermission().then(
(hasGranted) {
if (hasGranted == PermissionStatus.granted) {
refreshContacts();
}
},
);
},
...
Future<PermissionStatus> _checkPermission() async {
final Permission permission = Permission.contacts;
final status = await permission.request();
return status;
}
This always returns:
PermissionStatus.permanentlyDenied
but it doesn't open the permission question again.
Info.plist:
<key>NSContactsUsageDescription</key>
<string>This app requires access to display the contacts list. This will be used to import contacts automatically if necessary to the app contacts list.</string>

If a permission is permanently denied, it can only be enabled from the application settings (bar reinstalling the app). You can use the openAppSettings function to redirect the user to the settings.

Fixed by this issue on its repo: https://github.com/Baseflow/flutter-permission-handler/issues/718#event-5560838657

as per the current version of permission handler you need to add contact permission in Podfile and plist both files make sure you have added this.

Related

Why device token generated in every run of the flutter application?

I'm using firebase cloud messaging to send notifications to devices. The problem is that the device token regenrated and added to firestore with different id in every run of the application. I want it to be generated juste once for the first installation of the application.
this is my code :
Future init() async {
_firebaseMessaging.getToken().then((token) {
saveTokens(token);
});
}
Future<void> saveTokens(var token) async {
try {
await _firestore.collection('deviceTokens').add({
'token': token,
});
} catch (e) {
print(e);
}
}
this is how I call it in the main():
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
await _msgService.init();
// testFirestore();
FirebaseMessaging.onBackgroundMessage(_messageHandler);
this is _messageHandler function:
Future<void> _messageHandler(RemoteMessage message) async {
print(
'background message ${message.notification!.body} + ${message.notification!.title}');
}
Actually token only refresh on one of that cases:
The app deletes Instance ID
The app is restored on a new device
The user uninstalls/reinstall the app
The user clears app data.
So you need to check in your firebase collection if your token (getted on getToken()) is saved yet before add it. If it already exists in your database, don't save it.
For example:
Future<bool> doesTokenAlreadyExist(String token) async {
final QuerySnapshot result = await Firestore.instance
.collection('deviceTokens')
.where('token', isEqualTo: token)
.limit(1)
.getDocuments();
final List<DocumentSnapshot> documents = result.documents;
return documents.length == 1;
}
The registration token may change when:
The app is restored on a new device
The user uninstalls/reinstall the app
The user clears app data.
More :
Update from Play Store - Token remains same.
When close the application and reopen it - Token remains same.
I recommend you should record that token for the user every time your app launches. Then, you don't face any problems.
(add function to init state of home page of your app)

How to grant system permissions while writing flutter integration tests?

I am writing flutter integration tests https://docs.flutter.dev/cookbook/testing/integration/introduction
I can find and tap on my widgets perfectly fine but the problem arises when I have to click the system widgets to grant permissions in order to continue and test the remaining flow.
For example:
I tap a button then have to grant location permissions. At this moment the screen as below is shown
This screen is shown by the system and I have no key or way how to tap "While using the app".
Also the system screen might change between devices like ios and android and the text might be different as well.
What is the best practice to solve this issue? I am blocked from testing the remaining screens that use the required permissions..
What I tried so far?
I tried to grant the permissions manually for Android as a start but did not work
Future<void> grantRequiredAppPermissions() async {
if (!Platform.isAndroid) {
return;
}
const appPackageName = 'my.package';
print(
'We are going to manually grant the required permissions to the android package $appPackageName');
final Map<String, String> envVars = Platform.environment;
String adbPath = join(
envVars['ANDROID_SDK_ROOT'] ?? envVars['ANDROID_HOME']!,
'platform-tools',
Platform.isWindows ? 'adb.exe' : 'adb',
);
print('Using adb at $adbPath');
final permissions = [
'android.permission.READ_EXTERNAL_STORAGE',
'android.permission.WRITE_EXTERNAL_STORAGE',
'android.permission.ACCESS_FINE_LOCATION',
'android.permission.ACCESS_COARSE_LOCATION'
];
for (final permission in permissions) {
await Process.run(
adbPath, ['shell', 'pm', 'grant', appPackageName, permission]);
}
}
I was calling that function in my app/integration_test/driver.dart
import 'dart:io';
import 'package:integration_test/integration_test_driver_extended.dart';
import 'helpers/grant_required_permissions.dart';
Future<void> main() async {
await grantRequiredAppPermissions();
await integrationDriver();
}
but did not help at all. Also it is not a proper solution because even if it was going to work, would work only for android and i test also on ios devices.
You can try out the testing framework patrol https://pub.dev/packages/patrol
See the documentation here: https://patrol.leancode.co/
It let's you interact with the native ui like this:
await $.native.grantPermissionWhenInUse();
await $.native.grantPermissionOnlyThisTime();
await $.native.denyPermission();

Flutter Web Firebase Auth user is logged out after refresh

I have a Flutter Web App where the user is logged in via Firebase. The Problem is that every time the page is refreshed, the user is logged out.
Similiar for iOS: User stays logged in after refresh but is being logged out when completely closing the app.
I know there is this issue which says that it should be fixed with the latest version, but I am already on the supposedly fixed version:
firebase_core: ^1.20.0
firebase_auth: ^3.6.0
cloud_firestore: ^3.4.1
I am using the go_router and redirect the user if he is not logged in:
final GoRouter router = GoRouter(
key: Get.key,
urlPathStrategy: UrlPathStrategy.path,
redirect: (state) {
final String destination = state.location;
final bool isOnStartView = destination == '/start';
final bool isOnEmailFlow = state.subloc.contains('/email');
if (!isOnStartView && !isOnEmailFlow && !AuthService.isLoggedIn()) {
return '/start';
}
return null;
},
...
And this is my AuthService.isLoggedIn():
static final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
static User? get currentUser => FirebaseAuth.instance.currentUser;
static bool isLoggedIn() {
return _firebaseAuth.currentUser != null;
}
What am I missing here? It says everywhere that it should be fixed but it's not working for me.. Let me know if you need any more details!
Update to
dependencies:
firebase_auth: ^3.6.4
This bug is now resolved in lastest version.
Use stream -
FirebaseAuth.instance.authStateChanges()
FirebaseAuth.instance
.authStateChanges()
.listen((User? user) {
if (user != null) {
print(user.uid);
}
});
The one way to achieve that is you have to put a check and stored that value as a persistent data.
So you can use Shared Preferences and when the user login for the first time change the value of shared preference to true.
And then in initstate of login page get the value of shared preference and check if it is true or not. If true navigate to homepage otherwise to login page.
Note : this approach may not work in localhost but I m sure this will work when you host it to firebase hosting.
Hope this will help.

How call Permision request many times in flutter

I want call permissionRequest for getting user photos. When I call permissionRequest and user didn't allow permission, then I show alert and when in custom alert user tap in allow button I want show permissionRequest again.
For this I try using photo_manager or permission_handler package. But in both packages when I second time call permissionRequest() function not show system permissionRequest
void _showGeneralAlert() {
Alert.show(
context,
title: 'Allow photo access',
button: AlertButton(
title: 'Allow',
onTap: () {
Navigator.pop(context);
_request();
})
);
}
Future<void> _request() async {
_permissionStatus = await Permission.photos.request();
if (_permissionStatus != PermissionStatus.granted) {
_showGeneralAlert();
}
}
May be because you permanently denied permission,
For this case you need to ask user to take permission default way(ask to open app settings and take
permission)
if (await Permission.photos.isPermanentlyDenied) {
// The user opted to never again see the permission request dialog for this
// app. The only way to change the permission's status now is to let the
// user manually enable it in the //systemsettings.
Print("permission permanently denied ");
openAppSettings();
}
Update
If this case not work then check your permission status,
var status = await Permission.camera.status;
print("permission status"+status.toString());
If they print restricted or permanentlyDenied. Then you need to open setting,otherwise you can ask permission

How do I open instagram from flutter app?

I want to transite to Instagram profile, when I am tapping on button. I use this library url_launcher. But there I can use only Web Browser for this. What will I do, in order to reach my goal?
To open Native and WebView Instagram:
Add to your iOS/Runner/Info.plist:
<key>LSApplicationQueriesSchemes</key>
<array>
...
<string>instagram</string>
</array>
Install url_launcher ( https://pub.dev/packages/url_launcher )
Import
import 'package:url_launcher/url_launcher.dart';
Create some method like this;
Inside Widget for example:
_launchInstagram() async {
const nativeUrl = "instagram://user?username=severinas_app";
const webUrl = "https://www.instagram.com/severinas_app/";
if (await canLaunch(nativeUrl)) {
await launch(nativeUrl);
} else if (await canLaunch(webUrl)) {
await launch(webUrl);
} else {
print("can't open Instagram");
}
}
Well, I don't know if it's too late, but this works for me using the url_launcher library (tested on android only):
var url = 'https://www.instagram.com/<INSTAGRAM_PROFILE>/';
if (await canLaunch(url)) {
await launch(
url,
universalLinksOnly: true,
);
} else {
throw 'There was a problem to open the url: $url';
}
2022 update:
_launchInstagram() async {
var nativeUrl = "instagram://user?username=balkan.exe";
var webUrl = "https://www.instagram.com/balkan.exe";
try {
await launchUrlString(nativeUrl, mode: LaunchMode.externalApplication);
} catch (e) {
print(e);
await launchUrlString(webUrl, mode: LaunchMode.platformDefault);
}
}
I am not sure if you can launch the profile activity, unless you know the name (which might change in the future). But, if you can try launching the app by using Intent plugin:
Try adding the Intent plugin:
https://pub.dev/packages/intent
Add intent in you in your pubspec.yaml file.
You can call the specific app with the package name (in this case Instagram).
Intent()
..setAction(Action.ACTION_SHOW_APP_INFO)
..putExtra(Extra.EXTRA_PACKAGE_NAME, "instagram package name")
..startActivity().catchError((e) => print(e));
You can also make use Android flutter plugin for Intent:
https://pub.dev/packages/android_intent
This will support for Android :
Use it by specifying action, category, data and extra arguments for the intent. It does not support returning the result of the launched activity
For IOS url_launcher plugin can be used for deep linking
You can create a platform channel and use the intent to achieve that or use social_share package.
Install url_launcher ( https://pub.dev/packages/url_launcher )
They now have a mode parameter, which you set to LaunchMode.externalApplication;
Call it with the regular https url, if they have the app installed it will open the app, otherwise it will open in Safari. Don't worry about doing an app link and a website url, the package and the OS handle it accordingly.
For example, here are my links in our About section of the app to our socials..
SettingsListTileModel(
// This can be any website's url that has a corresponding app, we us it with Twitter, Facebook, Instagram, and Youtube.
onTap: () => launchUrl(
Uri.parse('https://www.instagram.com/forwheel_app/'),
mode: LaunchMode.externalApplication,
),
title: Text(
'Instagram',
style: context.bodyLarge),
),
trailing: Icon(
FontAwesomeIcons.squareArrowUpRight,
),
),