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
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)
I tried using your example code in my implementation, below is my podfile:
And here is my example.dart file:
when i give camera permisssion, then it works as expected without any issues;
I’m using:
flutter doctor -v): [✓]
14.5 23F79 darwin-arm64, locale en-IN)
web