Flutter camera plugin. Not initialise controller on iOS iPhone XR device
I have a camera widget in my Flutter application, and it initializes successfully on Android devices. However, on an iPhone XR running iOS, the camera initialization either doesn’t happen or has a significant delay. The code for my camera widget is as follows:
Here is your code properly formatted with 4 spaces indentation:
import 'dart:async';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../images/cached_image.dart';
import '../models/image.dart';
import '../theme/extensions/colors.dart';
import 'camera_controls.dart';
class Camera extends StatefulWidget {
const Camera({
super.key,
});
@override
State<Camera> createState() => _CameraState();
}
class _CameraState extends State<Camera>
with WidgetsBindingObserver, TickerProviderStateMixin {
CameraController? _controller;
XFile? _imageFile;
final bool _enableAudio = false;
double _minAvailableZoom = 1;
double _maxAvailableZoom = 1;
double _currentScale = 1;
double _baseScale = 1;
// Counting pointers (number of user fingers on screen)
int _pointers = 0;
var _cameras = <CameraDescription>[];
bool get _controllerIsAvailable => _controller != null;
bool get _isInitialized =>
_controllerIsAvailable && _controller!.value.isInitialized;
bool get _flashIsOff =>
_controllerIsAvailable && _controller!.value.flashMode == FlashMode.off;
bool get _isBackCamera =>
_controllerIsAvailable &&
_controller!.description.lensDirection == CameraLensDirection.back;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.black,
),
);
await _startCamera();
});
}
@override
void dispose() {
WidgetsBinding.instance.addPostFrameCallback((_) async {
await _controller?.dispose();
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
),
);
});
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
final cameraController = _controller;
// App state changed before we got the chance to initialize.
if (_controllerIsAvailable && !cameraController!.value.isInitialized) {
return;
}
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (state == AppLifecycleState.inactive) {
await cameraController?.dispose();
} else if (state == AppLifecycleState.resumed) {
await _initializeCameraController(cameraController!.description);
}
});
}
@override
Widget build(BuildContext context) => Stack(
children: [
if (_isInitialized)
Positioned.fill(
child: AspectRatio(
aspectRatio: _controller!.value.aspectRatio,
child: Listener(
onPointerDown: (_) => _pointers++,
onPointerUp: (_) => _pointers--,
child: CameraPreview(
_controller!,
child: LayoutBuilder(
builder: (context, constraints) => GestureDetector(
behavior: HitTestBehavior.opaque,
onScaleStart: _handleScaleStart,
onScaleUpdate: _handleScaleUpdate,
onTapDown: (details) async => onViewFinderTap(
details,
constraints,
),
),
),
),
),
),
),
if (_imageFile != null)
Positioned.fill(
child: CachedImage(
image: LocalImageVm(url: _imageFile!.path),
),
),
Align(
alignment: Alignment.topCenter,
child: ColoredBox(
color: Colors.black,
child: Row(
children: [
const SizedBox(width: 8),
if (_imageFile == null || _isBackCamera)
IconButton(
color: context.colorsTheme.background,
icon: Icon(
_flashIsOff ? Icons.flash_off : Icons.flash_on,
),
onPressed: () async {
await _toggleFlashlight();
},
),
],
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: CameraControls(
onPressedResetImage: _imageFile != null
? _resetImageFile
: () async => Navigator.of(context).pop(),
onPressedTakePicture:
_imageFile == null ? _onPressedTakePicture : null,
onPressedSwitchCamera: _imageFile != null
? () async {
Navigator.of(context).pop(_imageFile);
}
: () async {
await onNewCameraSelected();
},
),
),
],
);
Future<void> _startCamera() async {
_cameras = await availableCameras();
if (_cameras.isNotEmpty) {
await _initializeCameraController(_cameras[0]);
}
}
void _handleScaleStart(ScaleStartDetails details) {
_baseScale = _currentScale;
}
Future<void> _handleScaleUpdate(ScaleUpdateDetails details) async {
// When there are not exactly two fingers on screen don't scale
if (_pointers != 2) {
return;
}
_currentScale = (_baseScale * details.scale)
.clamp(_minAvailableZoom, _maxAvailableZoom);
await _controller!.setZoomLevel(_currentScale);
}
Future<void> _resetImageFile() async {
setState(() {
_imageFile = null;
});
}
Future<void> _toggleFlashlight() async {
try {
await _controller!.setFlashMode(
_controller!.value.flashMode == FlashMode.off
? FlashMode.torch
: FlashMode.off,
);
} on CameraException catch (e) {
_showCameraException(e);
}
}
Future<void> onViewFinderTap(
TapDownDetails details,
BoxConstraints constraints,
) async {
final cameraController = _controller;
final offset = Offset(
details.localPosition.dx / constraints.maxWidth,
details.localPosition.dy / constraints.maxHeight,
);
await cameraController!.setExposurePoint(offset);
await cameraController.setFocusPoint(offset);
}
Future<void> onNewCameraSelected() async {
final description = !_isBackCamera ? _cameras[0] : _cameras[1];
return _controller!.setDescription(description);
}
Future<void> _initializeCameraController(
CameraDescription cameraDescription,
) async {
await _controller?.dispose();
_controller = CameraController(
cameraDescription,
ResolutionPreset.max,
enableAudio: _enableAudio,
imageFormatGroup: ImageFormatGroup.jpeg,
);
await _controller!.setFlashMode(FlashMode.off);
_controller!.addListener(() {
if (mounted) {
setState(() {});
}
if (_controller!.value.hasError) {
throw Exception('Camera error ${_controller!.value.errorDescription}');
}
});
try {
await _controller!.initialize();
await _controller!
.getMaxZoomLevel()
.then((value) => _maxAvailableZoom = value);
await _controller!
.getMinZoomLevel()
.then((value) => _minAvailableZoom = value);
} on CameraException catch (e) {
_showCameraException(e);
}
if (mounted) {
setState(() {});
}
}
Future<void> _onPressedTakePicture() async {
final file = await takePicture();
if (mounted) {
setState(() {
_imageFile = file;
});
}
// await onPausePreview();
}
Future<void> setFlashMode(FlashMode mode) async {
try {
await _controller!.setFlashMode(mode);
} on CameraException catch (e) {
_showCameraException(e);
}
}
Future<XFile?> takePicture() async {
final cameraController = _controller;
if (!cameraController!.value.isInitialized) {
throw Exception('Error: select a camera first.');
}
if (cameraController.value.isTakingPicture) {
// A capture is already pending, do nothing.
return null;
}
try {
final file = await cameraController.takePicture();
return file;
} on CameraException catch (e) {
_showCameraException(e);
}
return null;
}
void _showCameraException(CameraException e) {
throw Exception('Error: ${e.code}n${e.description}');
}
}
I’ve tried different options for initialization and added variables to check isInitialised
, but despite this, the controller does not initialize on iOS.
flutter doctor output:
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.22.2, on macOS 14.5 23F79 darwin-arm64, locale
en-UA)
[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 15.4)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2024.1)
[✓] VS Code (version 1.90.2)
[✓] Connected device (4 available)
! Error: Browsing on the local area network for Hindenburg. Ensure the device is
unlocked and attached with a cable or associated with the same local area
network as this Mac.
The device must be opted into Developer Mode to connect wirelessly. (code -27)
[✓] Network resources
Александр Криницкий is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.