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
Related
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)
I have an app where when I logout with Firebase Auth, user's datas are still there, and I need to Hot restart the app to totally remove them.
I didn’t find a way to improve the signout method, so I want to try to rebuild app when user logout, or maybe delete the cache so user’s datas will no longer exist in app.
I have done some research but I can’t find any solution that can help me.
EDIT : Here's how I logout
Future<void> signOut() async {
await FirebaseAuth.instance
.signOut()
.then((value) => print('Sign Out'))
.onError((error, stackTrace) => print('Error in signed out'));
}
IconButton(
onPressed: () async {
await signOut();
Navigator.of(context, rootNavigator: true)
.pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) {
return const OnBoardingPage();
},
),
(_) => false,
);
},
icon: const Icon(Icons.logout))
I know that user's data are still there because I can display user.email in my onBoardingPage (where technically no data can be there because I logout previously).
Here a preview of my onBoardingPage :
After that, if I want to connect with another account, I will be connected to the previous user's session. The same if I want to create a new account, all new user's data will be into the previous connected user. To fix this problem, in development mode, I need to hot restart the app.
It seems like the user is not totally logout.
I use the health package in my App to sync data between my app and Google Fit / Apple Health. So far I have only testet Apple Health and it works perfectly fine if I grant all the necessary permissions. But I found one weird thing and I can't figure out how to fix it. So far I have only implemented and tested the integration with Apple Health.
So, I check if the app has the permission to read or write a certain data type, and then I read it or write it to Apple Health. If the permission was granted and the read / write operation was successful, I want to present that to the user. If the permission was not granted, I also want to present that to the user, so he knows what went wrong. In the HealthFactory class of the package there is the requestAuthorization method that I call to check whether the user has permission or not. The behavior I expected was that, if the permission is not granted, the return value of that method is false if the permission is not granted. But instead, it is always true. Also, the methods getHealthDataFromTypes and writeHealthData don't really indicate whether the permission is granted or not.
I implemented a few methods to showcase that:
class TestScreen extends StatelessWidget {
TestScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
child: Text("Health Permission"),
onPressed: () async {
final HealthFactory _health = HealthFactory();
await Permission.activityRecognition.request();
bool permission = await _health.requestAuthorization(
[HealthDataType.WEIGHT],
permissions: [HealthDataAccess.READ_WRITE],
);
print(permission);
},
),
ElevatedButton(
child: Text("Read Data"),
onPressed: () async {
final HealthFactory _health = HealthFactory();
DateTime untilDate = DateTime.now();
DateTime fromDate = DateTime(1970);
try {
List<HealthDataPoint> healthData =
await _health.getHealthDataFromTypes(
fromDate,
untilDate,
[HealthDataType.WEIGHT],
);
return print(healthData.toString());
} on Exception catch (e) {
print(e.toString());
rethrow;
}
},
),
ElevatedButton(
child: Text("Write Data"),
onPressed: () async {
final HealthFactory _health = HealthFactory();
try {
bool success = await _health.writeHealthData(
80,
HealthDataType.WEIGHT,
DateTime.now(),
DateTime.now(),
);
print(success);
} on Exception catch (e) {
print(e.toString());
}
},
),
],
),
),
);
}
}
This is my test screen.
The first button requests READ_WRITE permission for the datatype WEIGHT. At the first call, this window pops up:
When I click on "Don't Allow" in the top left corner, the permissions are NOT granted, but the method still returns true. If I go to the settings, no matter if I activate the permission or not, it always returns true.
The second button is supposed to read WEIGHT data from Apple Health. If the permission is granted, everything works fine. If the permission is NOT granted, it still "works", but jsut returns an empty list. This is pretty bad, because I can't indicate whether it failed or Apple Health just doesn't have any WEIGHT data.
The third button is supposed to write WEIGHT data into Apple Health. If the permission is granted, again, everthing works fine. If ther permission is NOT granted, the mehod returns false and doesn't write anything. This is already a better indicator than from the READ operation, but still, I can't indicate whether the permission wasn't granted or it was some other error.
So my question is: Is there any way to indicate at code level whether the permissions have been granted or not? I really need this to present to the user if the sync worked, and if not, why it didn't work.
Please take a look at the documentation https://pub.dev/documentation/health/latest/health/HealthFactory/hasPermissions.html please also verify but on IOS it looks like it returns null. Apple protects this fields and the package returns null as Apple HealthKit will not disclose if READ access has been granted for a data type due to privacy concern, this method can only return null to represent an undertermined status, if it is called on iOS with a READ or READ_WRITE access.:
You are right, it seems like HealthKit do not allow us to access the permission status. I personally consider that the user did not toggled the permission if I can't get its steps over the past month:
Future<bool> hasPermissionsOniOS() async {
final now = DateTime.now();
final lastMonth = DateTime(now.year, now.month - 1, now.day);
final stepsOfLastMonth =
await health.getTotalStepsInInterval(lastMonth, now);
return stepsOfLastMonth != null;
}
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.
It sounds stupid but I cant really properly logout of my app and that is because I use multiple FirebaseAnimatedList and route on my MaterialApp
routes: <String,WidgetBuilder>{
'/StartAppPage':(BuildContext context)=>new StartAppPage(),
'/LoginPage':(BuildContext context)=> new LoginPage(),
'/HomePage':(BuildContext context)=> new HomePage)
},
So the app checks for use and routes to HomePage or Login based on is there is a user or not.
My Home page has a FirebaseAnimatedList and
on my Home page there is a Logout button that do this
await googleSignIn.signOut();
await FirebaseAuth.instance.signOut();
await FirebaseDatabase.instance.goOffline();
return Navigator.pushReplacementNamed(context, '/StartApp');
to HomePage and logout the user.
But when the other user login again the List shows data of the old user and the list is usable messing my Database
How can I properly implement this and the setPersistance is off or on makes no difference
Solution: Keep a single instance of your firebase user through the app. I recommend a global variable
After signing out, what you can consider doing here is check on the current screen if the user is authenticated or not. If the user is still logged-in, display the data. If not, then navigate to a Login screen.
FirebaseAuth.instance
.idTokenChanges()
.listen((User? user) {
if (user == null) {
debugPrint('User is currently signed out!');
// TODO: navigate to Login screen?
} else {
debugPrint('User is signed in!');
// TODO: display data
}
});