Baseflow / flutter-permission-handler

Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.
https://baseflow.com
MIT License
2.01k stars 833 forks source link

8.1.4+1 iOS Thread 1: EXC_BAD_ACCESS (code=1, address=0xa51704330) carsh #638

Closed WXXXXH closed 3 years ago

WXXXXH commented 3 years ago

🐛 Bug Report

After denying permission, carsh(location 、 photo、storage )

image image

Version: 8.1.4+1

Platform:

mvanbeusekom commented 3 years ago

Hi @WXXXXH,

Thank you for reporting this bug, I have been looking into it but I cannot reproduce it unfortunately. We did have a bug throwing the EXC_BAD_ACCESS exception in version 8.1.4 and fixed it in version 8.1.4+1. I wanted to ask you if you could double check if you are really on version 8.1.4+1 and if you could make sure by running flutter clean followed by a flutter pub upgrade. I would really appreciate it if you could give this a try and let us know if you are still experiencing the error.

A few other remarks on your code:

  1. For requesting the location permissions you should not request all three permissions at the same time. The Permission.location is a place holder for the Permission.locationWhenInUse and Permission.locationAlways permissions. The permission_handler will determine which of the two permissions is the most appropriate to request. Requesting all three permissions (as you are doing in the checkAndRequestLocationPermission results into a deadlock on iOS and renders the permission_handler unusable;
  2. After you request the permissions you can use the result statuses to check the permission status, there is no need to use the await Permission.storage.status property (which makes another call into native code which might costs some extra performance). Meaning you can rewrite your code to this:
static checkAndRequestLocationPermission() async {
  PermissionStatus status = await Permission.location.request();

  if (status != PermissionStatus.granted) {
    openAppSettings();
    return;
  }
}

static checkAndRequestPhotoPermission() {
  Map<Permission, PermissionStatus> statuses = await [
    Permission.photos,
    Permission.storage,
    Permission.photosAddOnly,
  ].request();

  if (statuses[Permission.photos] != PermissionStatus.granted ||
      statuses[Permission.storage] != PermissionStatus.granted ||
      statuses[Permission.photosAddOnly] != PermissionStatus.granted) {
    openAppSettings();
    return;
  }
}

P.S. below is the code I used to try and reproduce the error. I have simply created a new Flutter App and modified the code as follows:

  1. Added the dependency to my pubspec.yaml:
    pubspec.yaml
name: permission_issue_638
description: A new Flutter project.
publish_to: 'none'
version: 1.0.0+1

environment:
  sdk: ">=2.12.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

  permission_handler: ^8.1.4+1

  cupertino_icons: ^1.0.2

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true

  1. Updated the ios/Podfile to enable the permissions needed:
    Podfile
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

project 'Runner', {
  'Debug' => :debug,
  'Profile' => :release,
  'Release' => :release,
}

def flutter_root
  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
  unless File.exist?(generated_xcode_build_settings_path)
    raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
  end

  File.foreach(generated_xcode_build_settings_path) do |line|
    matches = line.match(/FLUTTER_ROOT\=(.*)/)
    return matches[1].strip if matches
  end
  raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end

require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)

flutter_ios_podfile_setup

target 'Runner' do
  use_frameworks!
  use_modular_headers!

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end

post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)

    target.build_configurations.each do |config|
      # You can remove unused permissions here
      # for more infomation: https://github.com/BaseflowIT/flutter-permission-handler/blob/master/permission_handler/ios/Classes/PermissionHandlerEnums.h
      # e.g. when you don't need camera permission, just add 'PERMISSION_CAMERA=0'
      config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
        '$(inherited)',

        ## dart: PermissionGroup.calendar
        'PERMISSION_EVENTS=1',

        ## dart: PermissionGroup.reminders
        'PERMISSION_REMINDERS=1',

        ## dart: PermissionGroup.contacts
        'PERMISSION_CONTACTS=1',

        ## dart: PermissionGroup.camera
        'PERMISSION_CAMERA=1',

        ## dart: PermissionGroup.microphone
        'PERMISSION_MICROPHONE=1',

        ## dart: PermissionGroup.speech
        'PERMISSION_SPEECH_RECOGNIZER=1',

        ## dart: PermissionGroup.photos
        'PERMISSION_PHOTOS=1',

        ## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse]
        'PERMISSION_LOCATION=1',

        ## dart: PermissionGroup.notification
        'PERMISSION_NOTIFICATIONS=1',

        ## dart: PermissionGroup.mediaLibrary
        'PERMISSION_MEDIA_LIBRARY=1',

        ## dart: PermissionGroup.sensors
        'PERMISSION_SENSORS=1',

        ## dart: PermissionGroup.bluetooth
        'PERMISSION_BLUETOOTH=1',

        ## dart: PermissionGroup.appTrackingTransparency
        'PERMISSION_APP_TRACKING_TRANSPARENCY=1',

        ## dart: PermissionGroup.criticalAlerts
        'PERMISSION_CRITICAL_ALERTS=1',
      ]

    end
  end
end

  1. Added the necessary permissions to the ios/Runner/Info.plist file:
    Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>$(DEVELOPMENT_LANGUAGE)</string>
    <key>CFBundleExecutable</key>
    <string>$(EXECUTABLE_NAME)</string>
    <key>CFBundleIdentifier</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>permission_issue_638</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>$(FLUTTER_BUILD_NAME)</string>
    <key>CFBundleSignature</key>
    <string>????</string>
    <key>CFBundleVersion</key>
    <string>$(FLUTTER_BUILD_NUMBER)</string>
    <key>LSRequiresIPhoneOS</key>
    <true/>
    <key>UILaunchStoryboardName</key>
    <string>LaunchScreen</string>
    <key>UIMainStoryboardFile</key>
    <string>Main</string>
    <key>UISupportedInterfaceOrientations</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    <key>UISupportedInterfaceOrientations~ipad</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationPortraitUpsideDown</string>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    <key>UIViewControllerBasedStatusBarAppearance</key>
    <false/>

    <!-- Permission options for the `location` group -->
    <key>NSLocationWhenInUseUsageDescription</key>
    <string>Need location when in use</string>
    <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
    <string>Always and when in use!</string>
    <key>NSLocationUsageDescription</key>
    <string>Older devices need location.</string>
    <key>NSLocationAlwaysUsageDescription</key>
    <string>Can I have location always?</string>

    <!-- Permission options for the `mediaLibrary` group -->
    <key>NSAppleMusicUsageDescription</key>
    <string>Music!</string>
    <key>kTCCServiceMediaLibrary</key>
    <string>media</string>

    <!-- Permission options for the `calendar` group -->
    <key>NSCalendarsUsageDescription</key>
    <string>Calendars</string>

    <!-- Permission options for the `camera` group -->
    <key>NSCameraUsageDescription</key>
    <string>camera</string>

    <!-- Permission options for the `contacts` group -->
    <key>NSContactsUsageDescription</key>
    <string>contacts</string>

    <!-- Permission options for the `microphone` group -->
    <key>NSMicrophoneUsageDescription</key>
    <string>microphone</string>

    <!-- Permission options for the `speech` group -->
    <key>NSSpeechRecognitionUsageDescription</key>
    <string>speech</string>

    <!-- Permission options for the `sensors` group -->
    <key>NSMotionUsageDescription</key>
    <string>motion</string>

    <!-- Permission options for the `photos` group -->
    <key>NSPhotoLibraryUsageDescription</key>
    <string>photos</string>

    <!-- Permission options for the `reminder` group -->
    <key>NSRemindersUsageDescription</key>
    <string>reminders</string>

    <!-- Permission options for the `bluetooth` -->
    <key>NSBluetoothAlwaysUsageDescription</key>
    <string>bluetooth</string>
    <key>NSBluetoothPeripheralUsageDescription</key>
    <string>bluetooth</string>

    <!-- Permission options for the `appTrackingTransparency` -->
    <key>NSUserTrackingUsageDescription</key>
    <string>appTrackingTransparency</string>
</dict>
</plist>

  1. Updated the main.dart with the following code (matching your code samples):
    main.dart
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  void _requestLocationPermissions() async {
    Map<Permission, PermissionStatus> statusses = await [
      Permission.location,
      Permission.locationAlways,
      Permission.locationWhenInUse,
    ].request();

    if (await Permission.location.status != PermissionStatus.granted ||
        await Permission.locationAlways.status != PermissionStatus.granted ||
        await Permission.locationWhenInUse.status != PermissionStatus.granted) {
      print('Permissions are denied');
    }
  }

  void _requestPhotoPermissions() async {
    Map<Permission, PermissionStatus> statusses = await [
      Permission.photos,
      Permission.storage,
      Permission.photosAddOnly,
    ].request();

    if (await Permission.photos.status != PermissionStatus.granted ||
        await Permission.storage.status != PermissionStatus.granted ||
        await Permission.photosAddOnly.status != PermissionStatus.granted) {
      print('Permissions are denied');
    }
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        mainAxisSize: MainAxisSize.max,
        children: [
          FloatingActionButton(
            onPressed: _requestLocationPermissions,
            tooltip: 'Location permissions',
            child: Icon(Icons.location_on),
          ),
          SizedBox(
            height: 10,
          ),
          FloatingActionButton(
            onPressed: _requestPhotoPermissions,
            tooltip: 'Photo permissions',
            child: Icon(Icons.photo),
          ),
        ],
      ),
    );
  }
}

WXXXXH commented 3 years ago

Hi @WXXXXH,

Thank you for reporting this bug, I have been looking into it but I cannot reproduce it unfortunately. We did have a bug throwing the EXC_BAD_ACCESS exception in version 8.1.4 and fixed it in version 8.1.4+1. I wanted to ask you if you could double check if you are really on version 8.1.4+1 and if you could make sure by running flutter clean followed by a flutter pub upgrade. I would really appreciate it if you could give this a try and let us know if you are still experiencing the error.

A few other remarks on your code:

  1. For requesting the location permissions you should not request all three permissions at the same time. The Permission.location is a place holder for the Permission.locationWhenInUse and Permission.locationAlways permissions. The permission_handler will determine which of the two permissions is the most appropriate to request. Requesting all three permissions (as you are doing in the checkAndRequestLocationPermission results into a deadlock on iOS and renders the permission_handler unusable;
  2. After you request the permissions you can use the result statuses to check the permission status, there is no need to use the await Permission.storage.status property (which makes another call into native code which might costs some extra performance). Meaning you can rewrite your code to this:
static checkAndRequestLocationPermission() async {
  PermissionStatus status = await Permission.location.request();

  if (status != PermissionStatus.granted) {
    openAppSettings();
    return;
  }
}

static checkAndRequestPhotoPermission() {
  Map<Permission, PermissionStatus> statuses = await [
    Permission.photos,
    Permission.storage,
    Permission.photosAddOnly,
  ].request();

  if (statuses[Permission.photos] != PermissionStatus.granted ||
      statuses[Permission.storage] != PermissionStatus.granted ||
      statuses[Permission.photosAddOnly] != PermissionStatus.granted) {
    openAppSettings();
    return;
  }
}

P.S. below is the code I used to try and reproduce the error. I have simply created a new Flutter App and modified the code as follows:

  1. Added the dependency to my pubspec.yaml:

pubspec.yaml

  1. Updated the ios/Podfile to enable the permissions needed:

Podfile

  1. Added the necessary permissions to the ios/Runner/Info.plist file:

Info.plist

  1. Updated the main.dart with the following code (matching your code samples):

main.dart

import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  void _requestLocationPermissions() async {
    Map<Permission, PermissionStatus> statusses = await [
      Permission.location,
      Permission.locationAlways,
      Permission.locationWhenInUse,
    ].request();

    if (await Permission.location.status != PermissionStatus.granted ||
        await Permission.locationAlways.status != PermissionStatus.granted ||
        await Permission.locationWhenInUse.status != PermissionStatus.granted) {
      print('Permissions are denied');
    }
  }

  void _requestPhotoPermissions() async {
    Map<Permission, PermissionStatus> statusses = await [
      Permission.photos,
      Permission.storage,
      Permission.photosAddOnly,
    ].request();

    if (await Permission.photos.status != PermissionStatus.granted ||
        await Permission.storage.status != PermissionStatus.granted ||
        await Permission.photosAddOnly.status != PermissionStatus.granted) {
      print('Permissions are denied');
    }
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        mainAxisSize: MainAxisSize.max,
        children: [
          FloatingActionButton(
            onPressed: _requestLocationPermissions,
            tooltip: 'Location permissions',
            child: Icon(Icons.location_on),
          ),
          SizedBox(
            height: 10,
          ),
          FloatingActionButton(
            onPressed: _requestPhotoPermissions,
            tooltip: 'Photo permissions',
            child: Icon(Icons.photo),
          ),
        ],
      ),
    );
  }
}
image

And iOS 12.2 ,iOS 14 is OK

mvanbeusekom commented 3 years ago

@WXXXXH thank you for the additional information on the iOS version. I can indeed reproduce the problem on iOS 12.2. I will be looking into this and try to find a solution.

mvanbeusekom commented 3 years ago

Hi @WXXXXH,

I have just published a new hotfix release (version 8.1.4+2) which should fix this issue (see also PR #640). Thank you for pointing it out and supplying all information needed to reproduce the bug.

WXXXXH commented 3 years ago

Hi @WXXXXH,

I have just published a new hotfix release (version 8.1.4+2) which should fix this issue (see also PR #640). Thank you for pointing it out and supplying all information needed to reproduce the bug.

thanks ,is OK