skip to Main Content

I keep getting the following exception in flutter when I try to open the ReaderWidget of this example from the zxing library:

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: CameraException(channel-error, Unable to establish connection on channel: "dev.flutter.pigeon.camera_avfoundation.CameraApi.getAvailableCameras".)
#0      AVFoundationCamera.availableCameras (package:camera_avfoundation/src/avfoundation_camera.dart:76:7)
<asynchronous suspension>
#1      _ReaderWidgetState.initStateAsync.<anonymous closure> (package:flutter_zxing/src/ui/reader_widget.dart:199:29)
<asynchronous suspension>

The following part throws the exception (github link to package):

  @override
  Future<List<CameraDescription>> availableCameras() async {
    try {
      return (await _hostApi.getAvailableCameras())
          // See comment in messages.dart for why this is safe.
          .map((PlatformCameraDescription? c) => c!)
          .map(cameraDescriptionFromPlatform)
          .toList();
    } on PlatformException catch (e) {
      throw CameraException(e.code, e.message);
    }
  }

A similar exception occurs, when I try to access the media library.

What I found is that I have to add the following lines to the info.plist, which I did:

    <key>NSPhotoLibraryUsageDescription</key>
    <string>We need photo library access to scan barcodes</string>
    <key>NSCameraUsageDescription</key>
    <string>We need to access your camera for scanning QR codes</string>

When I open XCode -> Runner -> Runner -> Info I also see the entries listed and afterwords I did successfully flutter build ios.

Also, I am never prompted to grant access when I use the app. I just open the screen where the camera should be and see the error.

When I download and install the example, everything works as expected. The popup appears and I see the "Camera" option in the Settings for the app in my phone. I also tried copying the Info.plist of the example 1:1, but it also does not help.

Any ideas how to get the permissions working, or what additional steps I have to take to request the permission?

Here is the Code of my component:


class SetupStepScanQrCode extends StatefulWidget {
  final SetupStepperWidget setupStepper;

  const SetupStepScanQrCode(this.setupStepper, {Key? key}) : super(key: key);

  @override
  State<SetupStepScanQrCode> createState() => SetupStepScanQrCodeState();
}

class SetupStepScanQrCodeState extends State<SetupStepScanQrCode> {
  late final SetupStepperWidget setupStepper;
  Uint8List? createdCodeBytes;

  Code? result;
  Codes? multiResult;

  bool isMultiScan = false;

  bool showDebugInfo = true;
  int successScans = 0;
  int failedScans = 0;

  bool isManualProcess = false;
  bool qrCodeScanned = false;
  String? deviceName;
  String? devicePassword;

  @override
  void initState() {
    super.initState();
    setupStepper = widget.setupStepper;
  }

  void setDeviceDetails(name, password) {
    deviceName = name;
    devicePassword = password;
  }

  void connect() {
    setupStepper.setDeviceDetails(deviceName, devicePassword);
    setupStepper.next();
  }

  Future<Widget> _initializeReaderWidget() async {
    try {
      return ReaderWidget(
        onScan: _onScanSuccess,
        onScanFailure: _onScanFailure,
        onMultiScan: _onMultiScanSuccess,
        onMultiScanFailure: _onMultiScanFailure,
        onMultiScanModeChanged: _onMultiScanModeChanged,
        onControllerCreated: _onControllerCreated,
        isMultiScan: isMultiScan,
        scanDelay: Duration(milliseconds: isMultiScan ? 50 : 500),
        resolution: ResolutionPreset.high,
        lensDirection: CameraLensDirection.back,
        flashOnIcon: const Icon(Icons.flash_on),
        flashOffIcon: const Icon(Icons.flash_off),
        flashAlwaysIcon: const Icon(Icons.flash_on),
        flashAutoIcon: const Icon(Icons.flash_auto),
        galleryIcon: const Icon(Icons.photo_library),
        toggleCameraIcon: const Icon(Icons.switch_camera),
        actionButtonsBackgroundBorderRadius: BorderRadius.circular(10),
        actionButtonsBackgroundColor: Colors.black.withOpacity(0.5),
      );
    } catch (e) {
      print(e);
      return Center(child: Text('Error initializing camera (caught).'));
    }
  }

  @override
  Widget build(BuildContext context) {
    final isCameraSupported = defaultTargetPlatform == TargetPlatform.iOS ||
        defaultTargetPlatform == TargetPlatform.android;

    return BaseLayout(
      stepTitle,
      SizedBox(
        height: 300,
        width: 300,
        child: Stack(
          children: [
            FutureBuilder<Widget>(
              future: _initializeReaderWidget(),
              builder: (context, snapshot) {
                if (snapshot.connectionState == ConnectionState.done) {
                  if (snapshot.hasError) {
                    // Display error message or a fallback UI
                    return Center(
                        child: Text(
                            'Error initializing camera (snapshot.hasError)'));
                  }
                  // Return the ReaderWidget or error widget if no error
                  return snapshot.data!;
                } else {
                  // Return a loading indicator while waiting for the future to complete
                  return Center(child: CircularProgressIndicator());
                }
              },
            ),
            if (showDebugInfo)
              DebugInfoWidget(
                successScans: successScans,
                failedScans: failedScans,
                error: isMultiScan ? multiResult?.error : result?.error,
                duration: isMultiScan
                    ? multiResult?.duration ?? 0
                    : result?.duration ?? 0,
                onReset: _onReset,
              ),
          ],
        ),
      ),
      description: stepDescription,
      content: Column(
        children: stepContent,
      ),
    );
  }

  String get stepTitle {
    if (isManualProcess) {
      return 'Bluetoothverbindung herstellen';
    }
    return 'Scan QR-Code';
  }

  String get stepImage {
    if (isManualProcess) {
      return manualSetupBluetooth;
    }
    return cameraPlaceholder;
  }

  String get stepDescription {
    if (isManualProcess) {
      return '';
    }
    return 'Hier eine Anleitung.';
  }

  List<Widget> get stepContent {
    if (isManualProcess) {
      return [SetupStepSubBluetooth(this)];
    }
    return [
      qrCodeScanned
          ? Text(
              'QR-Code erkannt',
              textAlign: TextAlign.center,
              style: Theme.of(context)
                  .textTheme
                  .displayMedium!
                  .copyWith(color: Theme.of(context).colorScheme.primary),
            )
          : Text(
              'Kein QR-Code vorhanden?',
              textAlign: TextAlign.center,
              style: Theme.of(context)
                  .textTheme
                  .displaySmall!
                  .copyWith(color: const Color(0xFF7C7C7C)),
            ),
      SizedBox(height: qrCodeScanned ? paddingMain * 2 : paddingMain / 2),
      qrCodeScanned
          ? FilledButton(
              onPressed: () {
                // TODO: Disable if devicename and password are empty
                connect();
              },
              child: const Text('Jetzt verbinden'),
            )
          : OutlinedButton(
              onPressed: () {
                setState(() {
                  isManualProcess = true;
                });
              },
              child: const Text('Manuelle Eingabe starten'),
            ),
    ];
  }

  void _onControllerCreated(_, Exception? error) {
    if (error != null) {
      // Handle permission or unknown errors
      _showMessage(context, 'Error: $error');
    }
  }

  _onScanSuccess(Code? code) {
    setState(() {
      successScans++;
      result = code;
    });
  }

  _onScanFailure(Code? code) {
    setState(() {
      failedScans++;
      result = code;
    });
    if (code?.error?.isNotEmpty == true) {
      _showMessage(context, 'Error: ${code?.error}');
    }
  }

  _onMultiScanSuccess(Codes codes) {
    setState(() {
      successScans++;
      multiResult = codes;
    });
  }

  _onMultiScanFailure(Codes result) {
    setState(() {
      failedScans++;
      multiResult = result;
    });
    if (result.codes.isNotEmpty == true) {
      _showMessage(context, 'Error: ${result.codes.first.error}');
    }
  }

  _onMultiScanModeChanged(bool isMultiScan) {
    setState(() {
      this.isMultiScan = isMultiScan;
    });
  }

  _showMessage(BuildContext context, String message) {
    ScaffoldMessenger.of(context).hideCurrentSnackBar();
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }

  _onReset() {
    setState(() {
      successScans = 0;
      failedScans = 0;
    });
  }
}

Here is my Info.plist (that – according to XCode -> Targets Runner -> Build Settings -> Info.plist File is used in debug, profile and release)

<?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>CADisableMinimumFrameDurationOnPhone</key>
    <true/>
    <key>CFBundleDevelopmentRegion</key>
    <string>$(DEVELOPMENT_LANGUAGE)</string>
    <key>CFBundleDisplayName</key>
    <string>COMPANY Myapplication</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>myapplication</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>$(FLUTTER_BUILD_NAME)</string>
    <key>CFBundleSignature</key>
    <string>????</string>
    <key>CFBundleURLTypes</key>
    <array>
        <dict>
            <key>CFBundleTypeRole</key>
            <string>Editor</string>
            <key>CFBundleURLName</key>
            <string>auth0</string>
            <key>CFBundleURLSchemes</key>
            <array>
                <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
            </array>
        </dict>
    </array>
    <key>CFBundleVersion</key>
    <string>$(FLUTTER_BUILD_NUMBER)</string>
    <key>LSRequiresIPhoneOS</key>
    <true/>
    <key>NSBluetoothAlwaysUsageDescription</key>
    <string>Our app uses Bluetooth to configure your WiFi credentials.</string>
    <key>UIApplicationSupportsIndirectInputEvents</key>
    <true/>
    <key>UILaunchStoryboardName</key>
    <string>LaunchScreen</string>
    <key>UIMainStoryboardFile</key>
    <string>Main</string>
    <key>NSPhotoLibraryUsageDescription</key>
    <string>We need photo library access to scan barcodes</string>
    <key>NSCameraUsageDescription</key>
    <string>We need to access your camera for scanning QR codes</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>
</dict>
</plist>

2

Answers


  1. Probably that library your using has issue to open channeling to link native code to dart , which compiler mentioned (Unable to establish connection on channel)

    Login or Signup to reply.
  2. <key>NSCameraUsageDescription</key>
            <string>We need camera access for your profile picture and identity verification, boosting account security and personalizing your experience.</string>
    

    I tried using your example code in my implementation, below is my podfile:

        # Uncomment this line to define a global platform for your project
    platform :ios, '14.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
    
    # Enable use_frameworks! and use_modular_headers!
    use_frameworks!
    use_modular_headers!
    
    target 'Runner' do
      use_frameworks!
      use_modular_headers!
      flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
    
      target 'RunnerTests' do
        inherit! :search_paths
      end
    end
    
    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)',
                   'PERMISSION_LOCATION=1',
                   'PERMISSION_CONTACTS=1'
                ]
          config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
          # Disable bridging headers for module interfaces
          config.build_settings['SWIFT_MODULE_INTERFACE_HEADER_NAME'] = ''
        end
      end
    end
    

    And here is my example.dart file:

    import 'dart:typed_data';
    import 'package:flutter/foundation.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter_zxing/flutter_zxing.dart';
    
    class ScanResultWidget extends StatelessWidget {
      const ScanResultWidget({
        super.key,
        this.result,
        this.onScanAgain,
      });
    
      final Code? result;
      final Function()? onScanAgain;
    
      @override
      Widget build(BuildContext context) {
        return Center(
          child: Padding(
            padding: const EdgeInsets.all(20.0),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(
                  result?.format?.name ?? '',
                  style: Theme.of(context).textTheme.headlineSmall,
                ),
                const SizedBox(height: 20),
                Text(
                  result?.text ?? '',
                  style: Theme.of(context).textTheme.titleLarge,
                ),
                const SizedBox(height: 20),
                Text(
                  'Inverted: ${result?.isInverted}ttMirrored: ${result?.isMirrored}',
                  style: Theme.of(context).textTheme.bodyMedium,
                ),
                const SizedBox(height: 40),
                ElevatedButton(
                  onPressed: onScanAgain,
                  child: const Text('Scan Again'),
                ),
              ],
            ),
          ),
        );
      }
    }
    
    class DebugInfoWidget extends StatelessWidget {
      const DebugInfoWidget({
        super.key,
        required this.successScans,
        required this.failedScans,
        this.error,
        this.duration = 0,
        this.onReset,
      });
    
      final int successScans;
      final int failedScans;
      final String? error;
      final int duration;
    
      final Function()? onReset;
    
      @override
      Widget build(BuildContext context) {
        TextStyle? style =
        Theme.of(context).textTheme.bodySmall?.copyWith(color: Colors.white);
        return Align(
          alignment: Alignment.topCenter,
          child: Padding(
            padding: const EdgeInsets.all(10.0),
            child: ClipRRect(
              borderRadius: BorderRadius.circular(10),
              child: Container(
                color: Colors.black54,
                padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 0),
                child: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Flexible(
                      child: SingleChildScrollView(
                        scrollDirection: Axis.horizontal,
                        child: Row(
                          mainAxisSize: MainAxisSize.min,
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text('Success: $successScans', style: style),
                            const SizedBox(width: 10),
                            Text('Failed: $failedScans', style: style),
                            const SizedBox(width: 10),
                            Text('Duration: $duration ms', style: style),
                          ],
                        ),
                      ),
                    ),
                    const SizedBox(width: 10),
                    TextButton(
                      onPressed: onReset,
                      child: const Text('Reset'),
                    ),
                  ],
                ),
              ),
            ),
          ),
        );
      }
    }
    
    class UnsupportedPlatformWidget extends StatelessWidget {
      const UnsupportedPlatformWidget({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Center(
          child: Text(
            'This platform is not supported yet.',
            style: Theme.of(context).textTheme.titleLarge,
          ),
        );
      }
    }
    
    
    class BarEx extends StatefulWidget {
      const BarEx({super.key});
    
      @override
      State<BarEx> createState() => _BarExState();
    }
    
    class _BarExState extends State<BarEx> {
      @override
      Widget build(BuildContext context) {
        return const MaterialApp(
          title: 'Flutter Zxing Example',
          debugShowCheckedModeBanner: false,
          home: DemoPage(),
        );
      }
    }
    
    class DemoPage extends StatefulWidget {
      const DemoPage({super.key});
    
      @override
      State<DemoPage> createState() => _DemoPageState();
    }
    
    class _DemoPageState extends State<DemoPage> {
      Uint8List? createdCodeBytes;
    
      Code? result;
      Codes? multiResult;
    
      bool isMultiScan = false;
    
      bool showDebugInfo = true;
      int successScans = 0;
      int failedScans = 0;
    
      @override
      Widget build(BuildContext context) {
        final isCameraSupported = defaultTargetPlatform == TargetPlatform.iOS ||
            defaultTargetPlatform == TargetPlatform.android;
        return DefaultTabController(
          length: 2,
          child: Scaffold(
            appBar: AppBar(
              title: const TabBar(
                tabs: [
                  Tab(text: 'Scan Code'),
                  Tab(text: 'Create Code'),
                ],
              ),
            ),
            body: TabBarView(
              physics: const NeverScrollableScrollPhysics(),
              children: [
                if (kIsWeb)
                  const UnsupportedPlatformWidget()
                else if (!isCameraSupported)
                  const Center(
                    child: Text('Camera not supported on this platform'),
                  )
                else if (result != null && result?.isValid == true)
                    ScanResultWidget(
                      result: result,
                      onScanAgain: () => setState(() => result = null),
                    )
                  else
                    Stack(
                      children: [
                        ReaderWidget(
                          onScan: _onScanSuccess,
                          onScanFailure: _onScanFailure,
                          onMultiScan: _onMultiScanSuccess,
                          onMultiScanFailure: _onMultiScanFailure,
                          onMultiScanModeChanged: _onMultiScanModeChanged,
                          onControllerCreated: _onControllerCreated,
                          isMultiScan: isMultiScan,
                          scanDelay: Duration(milliseconds: isMultiScan ? 50 : 500),
                          resolution: ResolutionPreset.high,
                          lensDirection: CameraLensDirection.back,
                        ),
                        if (showDebugInfo)
                          DebugInfoWidget(
                            successScans: successScans,
                            failedScans: failedScans,
                            error: isMultiScan ? multiResult?.error : result?.error,
                            duration: isMultiScan
                                ? multiResult?.duration ?? 0
                                : result?.duration ?? 0,
                            onReset: _onReset,
                          ),
                      ],
                    ),
                if (kIsWeb)
                  const UnsupportedPlatformWidget()
                else
                  ListView(
                    children: [
                      WriterWidget(
                        messages: const Messages(
                          createButton: 'Create Code',
                        ),
                        onSuccess: (result, bytes) {
                          setState(() {
                            createdCodeBytes = bytes;
                          });
                        },
                        onError: (error) {
                          _showMessage(context, 'Error: $error');
                        },
                      ),
                      if (createdCodeBytes != null)
                        Image.memory(createdCodeBytes ?? Uint8List(0), height: 400),
                    ],
                  ),
              ],
            ),
          ),
        );
      }
    
      void _onControllerCreated(_, Exception? error) {
        if (error != null) {
          // Handle permission or unknown errors
          _showMessage(context, 'Error: $error');
        }
      }
    
      _onScanSuccess(Code? code) {
        setState(() {
          successScans++;
          result = code;
        });
      }
    
      _onScanFailure(Code? code) {
        setState(() {
          failedScans++;
          result = code;
        });
        if (code?.error?.isNotEmpty == true) {
          _showMessage(context, 'Error: ${code?.error}');
        }
      }
    
      _onMultiScanSuccess(Codes codes) {
        setState(() {
          successScans++;
          multiResult = codes;
        });
      }
    
      _onMultiScanFailure(Codes result) {
        setState(() {
          failedScans++;
          multiResult = result;
        });
        if (result.codes.isNotEmpty == true) {
          _showMessage(context, 'Error: ${result.codes.first.error}');
        }
      }
    
      _onMultiScanModeChanged(bool isMultiScan) {
        setState(() {
          this.isMultiScan = isMultiScan;
        });
      }
    
      _showMessage(BuildContext context, String message) {
        ScaffoldMessenger.of(context).hideCurrentSnackBar();
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text(message)),
        );
      }
    
      _onReset() {
        setState(() {
          successScans = 0;
          failedScans = 0;
        });
      }
    }
    

    when i give camera permisssion, then it works as expected without any issues;

    I’m using:

    • Xcode: Version 15.4 (15F31d)
    • Doctor summary (to see all details, run
      flutter doctor -v): [✓]
    • Flutter (Channel stable, 3.19.3, on macOS
      14.5 23F79 darwin-arm64, locale en-IN)
    • [✓] Android toolchain – develop for Android devices (Android SDK version 34.0.0)
    • [✓] Xcode, develop for iOS and macOS (Xcode 15.4)
    • [✓] Chrome – develop for the
      web
    • [✓] Android Studio (version 2023.3)
    • [✓] VS Code (version 1.81.1)
    • [✓] Connected device (5 available)
    • [✓] Network resources

    File Image

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search