import 'dart:developer' as developer;
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
// Minimal reproduction: switching the focus mode to FocusMode.auto throws
// "StateError: Bad state: No element" in camera_android_camerax when the current
// focus-metering action has no AF metering points. Tap "Reproduce (StateError)"
// and watch step 5. See the issue description for the root cause.
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'camera_focus_issue',
theme: ThemeData(colorSchemeSeed: Colors.indigo),
home: const ReproPage(),
);
}
}
class ReproPage extends StatefulWidget {
const ReproPage({super.key});
@override
State<ReproPage> createState() => _ReproPageState();
}
class _ReproPageState extends State<ReproPage> {
CameraController? _controller;
final List<String> _log = <String>[];
// Focus/exposure points use normalized coordinates in [0, 1].
static const Offset _center = Offset(0.5, 0.5);
@override
void initState() {
super.initState();
_setupCamera();
}
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
void _addLog(String message) {
debugPrint('[repro] $message');
if (!mounted) return;
setState(() => _log.insert(0, message));
}
Future<void> _setupCamera() async {
try {
_addLog('Calling availableCameras()...');
final List<CameraDescription> cameras = await availableCameras();
if (cameras.isEmpty) {
_addLog('No cameras found');
return;
}
final CameraDescription description = cameras.firstWhere(
(CameraDescription c) => c.lensDirection == CameraLensDirection.back,
orElse: () => cameras.first,
);
final CameraController controller = CameraController(
description,
ResolutionPreset.medium,
enableAudio: false,
);
await controller.initialize();
if (!mounted) {
await controller.dispose();
return;
}
setState(() => _controller = controller);
_addLog(
'initialized: focusPointSupported=${controller.value.focusPointSupported}, '
'exposurePointSupported=${controller.value.exposurePointSupported}',
);
} catch (e, st) {
_addLog('Setup failed: $e');
developer.log('setup failed', name: 'repro', error: e, stackTrace: st);
}
}
/// Runs a single step (for manual inspection). Exceptions are caught and
/// logged so the rest of the UI keeps working.
Future<void> _step(String label, Future<void> Function() action) async {
final CameraController? c = _controller;
if (c == null || !c.value.isInitialized) {
_addLog('controller not initialized');
return;
}
try {
await action();
_addLog('OK: $label');
} catch (e, st) {
_addLog('${e.runtimeType}: $label -> $e');
developer.log(label, name: 'repro', error: e, stackTrace: st);
}
}
/// Runs the 5 steps in order to reproduce the StateError.
///
/// 1. setExposurePoint(center) - build an action with only an AE point.
/// 2. setFocusPoint(center) - now the action has both AE and AF points.
/// 3. setFocusMode(locked) - lock while an AF point exists, so
/// _defaultFocusPointLocked stays false and
/// _currentFocusMode becomes locked. Without
/// this, step 5 early-returns and does not crash.
/// 4. setFocusPoint(null) - clear the AF point, leaving only the AE point
/// (meteringPointsAf empty, action non-null).
/// 5. setFocusMode(auto) - the auto branch calls .first on the empty AF
/// list -> StateError: Bad state: No element.
Future<void> _reproduce() async {
final CameraController? c = _controller;
if (c == null || !c.value.isInitialized) {
_addLog('controller not initialized');
return;
}
_addLog('=== reproduction sequence start ===');
await _step('1) setExposurePoint(center)', () => c.setExposurePoint(_center));
await _step('2) setFocusPoint(center)', () => c.setFocusPoint(_center));
await _step('3) setFocusMode(locked)', () => c.setFocusMode(FocusMode.locked));
await _step('4) setFocusPoint(null) [clear AF]', () => c.setFocusPoint(null));
await _step('5) setFocusMode(auto) [StateError expected here]',
() => c.setFocusMode(FocusMode.auto));
_addLog('=== reproduction sequence end ===');
}
@override
Widget build(BuildContext context) {
final CameraController? c = _controller;
final bool ready = c != null && c.value.isInitialized;
return Scaffold(
appBar: AppBar(title: const Text('camera_focus_issue repro')),
body: Column(
children: <Widget>[
Expanded(
flex: 3,
child: Container(
color: Colors.black,
alignment: Alignment.center,
child: ready
? CameraPreview(c)
: const Text(
'Initializing camera...',
style: TextStyle(color: Colors.white),
),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: Wrap(
spacing: 8,
runSpacing: 4,
alignment: WrapAlignment.center,
children: <Widget>[
FilledButton(
onPressed: ready ? _reproduce : null,
child: const Text('Reproduce (StateError)'),
),
OutlinedButton(
onPressed: ready
? () => _step('setExposurePoint(center)',
() => c.setExposurePoint(_center))
: null,
child: const Text('setExposurePoint'),
),
OutlinedButton(
onPressed: ready
? () => _step('setFocusPoint(center)',
() => c.setFocusPoint(_center))
: null,
child: const Text('setFocusPoint'),
),
OutlinedButton(
onPressed: ready
? () => _step('setFocusPoint(null)',
() => c.setFocusPoint(null))
: null,
child: const Text('setFocusPoint(null)'),
),
OutlinedButton(
onPressed: ready
? () => _step('setFocusMode(locked)',
() => c.setFocusMode(FocusMode.locked))
: null,
child: const Text('setFocusMode(locked)'),
),
OutlinedButton(
onPressed: ready
? () => _step('setFocusMode(auto)',
() => c.setFocusMode(FocusMode.auto))
: null,
child: const Text('setFocusMode(auto)'),
),
],
),
),
const Divider(height: 1),
Expanded(
flex: 2,
child: ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: _log.length,
itemBuilder: (BuildContext context, int index) {
final String line = _log[index];
final bool isError = line.contains('Error') ||
line.contains('Exception') ||
line.contains('failed');
return Text(
line,
style: TextStyle(
fontFamily: 'monospace',
fontSize: 12,
color: isError ? Colors.red : null,
),
);
},
),
),
],
),
);
}
}
What package does this bug report belong to?
camera
What target platforms are you seeing this bug on?
Android
Have you already upgraded your packages?
Yes
Dependency versions
pubspec.lock
Steps to reproduce
AndroidCameraCameraX.setFocusMode(FocusMode.auto)calls.firston the current focus-metering action's AF metering points without anisEmptyguard. When that list is empty, it throwsStateError: Bad state: No element.The attached minimal app reproduces it deterministically by driving a normal
CameraControllerthrough five calls (same call path as a real app):setExposurePoint(Offset(0.5, 0.5))— action has only an AE pointsetFocusPoint(Offset(0.5, 0.5))— action now also has an AF pointsetFocusMode(FocusMode.locked)— lock while an AF point existssetFocusPoint(null)— clear the AF point (AF list becomes empty, action stays non-null)setFocusMode(FocusMode.auto)— throwsStateError: Bad state: No elementStateErroron step 5.Two conditions matter and are easy to miss:
locked) is required. Without a priorlocked,setFocusMode(auto)early-returns atif (_currentFocusMode == mode) return;(the initial mode is alreadyauto) and never reaches the faulty line._defaultFocusPointLocked == false, which is the case when the lock happened while an AF point existed (so the plugin did not fall back to its default center lock).Expected results
setFocusMode(FocusMode.auto)should succeed even when the current focus-metering action has no AF points — exactly like theFocusMode.lockedbranch of the same method, which already guards this withisEmpty.Actual results
Step 5, setFocusMode(FocusMode.auto), throws and never completes:
StateError: Bad state: No element
The stack trace (see Logs) points to AndroidCameraCameraX.setFocusMode, where the FocusMode.auto branch reads the AF metering points without an isEmpty guard:
https://github.com/flutter/packages/blob/1b56cdeeb733029126cd70c755187d6adf743421/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart#L632-L637
The FocusMode.locked branch just below already guards the same access with isEmpty; the auto branch is simply missing it.
Code sample
Code sample
Screenshots or Videos
Screenshots / Video demonstration
2026-06-10.22.46.00.mov
Logs
Logs
Flutter Doctor output
Doctor output