I am trying to use a simple Flutter plugin (speech recognition wrapper) and have no idea how to request the appropriate permissions on Android 23 or newer. In the Dart part I have:
Future requestPermissions() =>
_channel.invokeMethod("speech.requestPermissions");
In the Android part:
public class SpeechRecognitionPlugin implements MethodCallHandler, RecognitionListener,
PluginRegistry.RequestPermissionResultListener {
Plugin registration:
public static void registerWith(Registrar registrar) {
final MethodChannel channel = new MethodChannel(registrar.messenger(), "speech_recognition");
SpeechRecognitionPlugin speechRecognitionPlugin = new
SpeechRecognitionPlugin(registrar.activity(), channel);
channel.setMethodCallHandler(speechRecognitionPlugin);
registrar.addRequestPermissionResultListener(speechRecognitionPlugin);
}
Method call:
else if (call.method.equals("speech.requestPermissions")) {
Log.d(LOG_TAG, "speech.requestPermissions");
if (ActivityCompat.shouldShowRequestPermissionRationale(activity,
Manifest.permission.RECORD_AUDIO)) {
Toast.makeText(activity.getApplicationContext(), "This application needs the Record Audio permission for recognition to work", Toast.LENGTH_LONG).show();
} else {
Log.d(LOG_TAG, "Requesting permissions");
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.RECORD_AUDIO},
1);
}
result.success(hasRecordAudioPermission());
Result callback:
#Override
public boolean onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) {
boolean granted = false;
switch (requestCode) {
case 1: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
granted = true;
}
speechChannel.invokeMethod("speech.onPermission", granted);
return true;
}
}
return false;
}
From logcat I see that the "speech.requestPermissions" call happens, but standard Android system permission request is not shown, just this in the logcat may be related:
D/ViewRootImpl(21171): #1 mView = android.widget.LinearLayout{64f050b
V.E...... ......I. 0,0-0,0 #102039d android:id/toast_layout_root}
D/ViewRootImpl(21171): MSG_RESIZED_REPORT: ci=Rect(0, 0 - 0, 0) vi=Rect(0, 0 - 0, 0) or=1
D/ViewRootImpl(21171): #3 mView = null
What is the correct way to request permissions for Flutter plugins?
EDIT: This does not apply to the first run, when the dialog shows correctly, but to subsequent runs when the user did not grant the permission at first or revoked it via settings. I realize that changes the question significantly (making it appear as edge case), but Android permissions are not supposed to work that way.
EDIT: The permissions are present in AndroidManifest.xml
Use Permission plugin for flutter
Request permission
import 'package:permissions_plugin/permissions_plugin.dart';
Map<Permission, PermissionState> permission = await PermissionsPlugin
.requestPermissions([
Permission.ACCESS_FINE_LOCATION,
Permission.ACCESS_COARSE_LOCATION,
Permission.READ_PHONE_STATE
]);
Check status permission
import 'package:permissions_plugin/permissions_plugin.dart';
Map<Permission, PermissionState> permission = await PermissionsPlugin
.checkPermissions([
Permission.ACCESS_FINE_LOCATION,
Permission.ACCESS_COARSE_LOCATION,
Permission.READ_PHONE_STATE
]);
I have this working for location permissions. The only thing I'm doing differently is in your method call here:
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.RECORD_AUDIO},
1);
Instead of using 'ActivityCompat' I store the registrar in a local final variable and I'm doing the following :
registrar.activity().requestPermissions(activity,
new String[]{Manifest.permission.RECORD_AUDIO},
1);
EDIT: Also make sure that you have included the relevant permissions in your AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Add this -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- Flutter stuff --->
</manifest>
Let's say you want to request camera permission using permission_handler package.
In pubspec.yaml file:
permission_handler: ^8.0.0+2
(For Android) Add the permission to android/app/src/main/AndroidManifest.xml file
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
...
</manifest>
(For iOS),
(i) Add this to your info.plist file
<key>NSCameraUsageDescription</key>
<string>App needs camera permission to work</string>
(ii) Add 'PERMISSION_CAMERA=1' to your Podfile.
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
## Add the following line.
'PERMISSION_CAMERA=1'
]
end
end
end
Request the permission:
final status = await Permission.camera.request();
if (status == PermissionStatus.granted) {
print('Permission granted');
} else if (status == PermissionStatus.denied) {
print('Permission denied. Show a dialog and again ask for the permission');
} else if (status == PermissionStatus.permanentlyDenied) {
print('Take the user to the settings page.');
await openAppSettings();
}
Related
So I have the following error:
√ Built build\app\outputs\flutter-apk\app-debug.apk.
D/FlutterLocationService( 6191): Creating service.
D/FlutterLocationService( 6191): Binding to location service.
Lost connection to device.
I also don't know why it says D/FlutterLocationService, instead of D/FlutterGeolocator.
I heard you now need to manually ask for permission, but have zero clue how to do that. How should I add the code for Geolocator permissions to this code?
AppStateProvider() {
_saveDeviceToken();
FirebaseMessaging.onMessage;
FirebaseMessaging.onMessageOpenedApp;
_setCustomMapPin();
_getUserLocation();
_listemToDrivers();
Geolocator.getPositionStream().listen(_updatePosition);
}
// ANCHOR: MAPS & LOCATION METHODS
_updatePosition(Position newPosition) {
position = newPosition;
notifyListeners();
}
Future<Position> _getUserLocation() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
position = await Geolocator.getCurrentPosition();
List<Placemark> placemark =
await placemarkFromCoordinates(position.latitude, position.longitude);
you simply add permission request before you get user location
so inside the _getUserLocation() and before
position = await Geolocator.getCurrentPosition();
add below code.
final permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
// Permissions are denied, next time you could try
// requesting permissions again (this is also where
// Android's shouldShowRequestPermissionRationale
// returned true. According to Android guidelines
// your App should show an explanatory UI now.
return Future.error('Location permissions are denied');
}
}
if (permission == LocationPermission.deniedForever) {
// Permissions are denied forever, handle appropriately.
return Future.error(
'Location permissions are permanently denied, we cannot request permissions.');
}
And you can probably check https://pub.dev/packages/geolocator as well
I have the following code to handle storage permissions :
private void getGalleryPermission() {
Log.d(TAG, "getGalleryPermission: checking permissions.");
String[] permissions = { WRITE_STORAGE, READ_STORAGE };
if (ContextCompat.checkSelfPermission(requireActivity(), WRITE_STORAGE) == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "getGalleryPermission: write storage is granted.");
if (ContextCompat.checkSelfPermission(requireActivity(), READ_STORAGE) == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "getGalleryPermission: read storage is granted.");
setupRecyclerView();
}
else {
Log.d(TAG, "getGalleryPermission: asking to read the storage.");
requestPermissions(permissions, REQUEST_STORAGE_PERMISSION_RESULT);
}
}
else {
Log.d(TAG, "getGalleryPermission: asking to write the storage.");
requestPermissions(permissions, REQUEST_STORAGE_PERMISSION_RESULT);
}
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
Log.d(TAG, "onRequestPermissionsResult: request code " + requestCode);
if (requestCode == REQUEST_STORAGE_PERMISSION_RESULT) {
Log.d(TAG, "onRequestPermissionsResult: request code is correct.");
if (grantResults.length > 0) {
for (int grantResult : grantResults) {
if (grantResult != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(requireActivity(), "Don't have permission to access the Gallery !", Toast.LENGTH_SHORT).show();
break;
}
}
// Setup the gallery once the permissions are granted
setupRecyclerView();
}
}
}
I am testing this code on several of my phones, and it's working perfectly. However, on my Huawei, the listFiles() method returns null on the /storage/emulated/0 directory.
I have checked in the phone Settings + in logs, and the storage permissions are granted (like on other working phones).
What can prevent the files from being read despite granted permissions ?
On some phones, is it possible that the /storage/emulated/0 is not the correct directory to target to get all gallery files ?
PS : i have checked the directory from the Files application in my phone and it's not empty (DCIM, etc.... are inside).
Thanks
is the Huawei device which returns null running Android 10? It seems that for that path Android returns null since API 29. You might want to check this question here.
As i am using MapBox in my application .i don't know how to get the current position to open Navigation_MapBox for start navigation with the current position as origin to the destination .thanks
Use geolcator library
add it to pubspec yaml file
import it
import 'package:geolocator/geolocator.dart';
then
Position position = await Geolocator().getLastKnownPosition(desiredAccuracy: LocationAccuracy.high);
print("my location is ${position.latitude} ${position.longitude} ");
To get user location you need to run this
import 'package:geolocator/geolocator.dart';
/// Determine the current position of the device.
///
/// When the location services are not enabled or permissions
/// are denied the `Future` will return an error.
Future<Position> _determinePosition() async {
bool serviceEnabled;
LocationPermission permission;
// Test if location services are enabled.
serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
// Location services are not enabled don't continue
// accessing the position and request users of the
// App to enable the location services.
return Future.error('Location services are disabled.');
}
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
// Permissions are denied, next time you could try
// requesting permissions again (this is also where
// Android's shouldShowRequestPermissionRationale
// returned true. According to Android guidelines
// your App should show an explanatory UI now.
return Future.error('Location permissions are denied');
}
}
if (permission == LocationPermission.deniedForever) {
// Permissions are denied forever, handle appropriately.
return Future.error(
'Location permissions are permanently denied, we cannot request permissions.');
}
// When we reach here, permissions are granted and we can
// continue accessing the position of the device.
return await Geolocator.getCurrentPosition();
}
This is a rewrite of my original post because I have found that, contrary to Permissions overview:
a) The secondary permission dialog is never displayed
b) After
clicking "Deny" for the initial permission dialog, it is never shown
again.
Therefore, if I click deny, I can never allow calendar permission.
I set permissions in the manifest:
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.READ_CALENDAR"/>
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
On start, I check for calendar permissions in MainActivity because they are necessary for the user to add appointments via my app and make them available to Google calendar (this works, if I allow, my app adds the calendar):
AccessCalendar accessCalendar = new AccessCalendar();
if(accessCalendar.requestCalendarPermissions(mContext, this)){
/**
* calendar read and write permissions not allowed: explain why needed
*/
Log.d(TAG, "calendar read and write permissions not allowed: explain why needed");
ConstraintLayout constraintLayout = (ConstraintLayout) findViewById(R.id.constraintLayout);
Snackbar.make(constraintLayout, R.string.write_calendar_permission_explanation, Snackbar.LENGTH_LONG).show();
DialogFragment permissionDialog = new CalendarPermissionExplanationFragment();
try {
permissionDialog.show(getSupportFragmentManager(), "CalendarPermissionExplanationFragment");
}catch (Exception e){
Log.d(TAG, "exception e is: " + e);
}
}else{
/**
* calendar read and write permissions allowed! No need to explain
*/
Log.d(TAG, "calendar read and write permissions allowed! No need to explain");
}
AccessCalendar.requestCalendarPermissions is (pretty much plagarized from Request App Permissions):
public boolean requestCalendarPermissions(Context context, Activity activity){
Log.d(TAG, "Entered: requestCalendarPermissions");
boolean explain = false;
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CALENDAR)
!= PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(context,
Manifest.permission.WRITE_CALENDAR) != PackageManager.PERMISSION_GRANTED ) {
// Permission is not granted
Log.d(TAG, "read and write permission not granted");
if (ActivityCompat.shouldShowRequestPermissionRationale(activity,
Manifest.permission.READ_CALENDAR) &&
ActivityCompat.shouldShowRequestPermissionRationale(activity,
Manifest.permission.WRITE_CALENDAR)) {
Log.d(TAG, "shouldShowRequestPermissionRationale is true");
// Show an explanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
Toast.makeText(context, R.string.write_calendar_permission_explanation, Toast.LENGTH_LONG).show();
explain = true;
} else {
Log.d(TAG, "shouldShowRequestPermissionRationale is false");
// No explanation needed; request the permission
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR},
MY_PERMISSIONS_REQUEST_CALENDAR);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
}else{
//permission is granted, so set permission to true
Log.d(TAG, "read and write calendar permissions granted");
}
return explain;
}
Due to some problems, I am forced to use Toast to provide an explanation. I am now presented with the initial permissions dialog who's request attempt, Deny or Allow, is handled by MainActivity.onRequestPermissionsResult():
public void onRequestPermissionsResult(int requestCode,
String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
Log.d(TAG, "Entered: onRequestPermissionsResult");
Log.d(TAG, "requestCode is: " + requestCode);
Log.d(TAG, "permissions is: " + permissions);
Log.d(TAG, "grantResults is: " + grantResults);
int cntPermissions = grantResults.length;
if(cntPermissions > 0) {
for (int i = 0; i < cntPermissions; i++) {
Log.d(TAG, "permissions[" + i + "] is: " + permissions[i]);
}
}
int cntGrantResults = grantResults.length;
if(cntGrantResults > 0) {
for (int i = 0; i < cntGrantResults; i++) {
Log.d(TAG, "grantResults[" + i + "] is: " + grantResults[i]);
}
}
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_CALENDAR:
Log.d(TAG, "Entered: case MY_PERMISSIONS_REQUEST_CALENDAR");
if(cntPermissions > 0) {
if(permissions[1].equals("android.permission.WRITE_CALENDAR") && grantResults[1] == 0){
Toast.makeText(this, R.string.write_calendar_permission, Toast.LENGTH_LONG).show();
AccessCalendar accessCalendar = new AccessCalendar();
accessCalendar.createCalendar(mContext, this);
//get calendar information again to see if petrecords calendar was created
Log.d(TAG, "get the calendars in the system to see if petrecords now exists");
accessCalendar.getCalendars(mContext, this);
}else{
Toast.makeText(this, R.string.error_no_write_calendar_permission, Toast.LENGTH_LONG).show();
Log.d(TAG, "create alertdialog to explain why the permission is required");
}
}else{
Log.d(TAG, "no permissions returned");
}
break;
default:
Log.d(TAG, "illegal requestCode value");
break;
}
return;
}
In that case, a new calendar is created. Otherwise, the user proceeds after Denying. Later, the user is presented with Appointments and may, at that point, desire to add an appointment via my app and so clicks the Appointment tab and then the Manage Appointments button. At that point, precisely the same code is entered to check calendar permissions as shown above.
At that point I expect to be presented with the secondary permissions dialog, but no, all I get is my explanation. I would also expect to see the secondary permissions dialog when I relaunch my app after initially Denying, but no.
So, why doesn't Android ever present the secondary permissions dialog?
The shouldShowRequestPermissionRationale() method implies that the permission was rejected before and that you should explain why you're asking for the permissions.
For this you should use a Dialog or Snackbar with an action the user can use to trigger the permission request again.
If you're app doesn't work without this permission, you might want to show a bigger screen explaining that the app doesn't work without.
You could even ignore the shouldShowRequestPermissionRationale() flag and just show the permission request again and again. That's very annoying though.
I/chromium: [INFO:CONSOLE(0)] "Not allowed to load local resource:
file:///android_asset/webkit/android-weberror.png", source:
data:text/html,chromewebdata (0) I found many methods to solve
it,like:
webView.loadUrl("file:///android_asset/assets/wwww/js/mypage.html");
webView.loadUrl("file:///assets/mypage.html");
webView.loadUrl("file:///android_asset/mypage.html");
All of them can not work.
Be sure that your file mypage.html really exists inside /assets directory:
webView.loadUrl("file:///android_asset/mypage.html");
you need to declare the permission into your AndroidManifest.xml:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Using Android 6.0+ must request WRITE_EXTERNAL_STORAGE permissions manually:
private void checkExternalStoragePermission() {
int permissionCheck = ContextCompat.checkSelfPermission(
this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
Log.i("Message", "PERMISSION NOT SET.");
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, 225);
} else {
Log.i("Message", "Permission OK!");
}
}