fix: extract manual entry dialog into StatefulWidget

Fixes TextEditingController used after being disposed  the controller
was being manually disposed while the dialog's TextField still held a
reference during the pop animation. Now the dialog is its own
StatefulWidget so dispose() is called automatically by the framework
at the right time.
This commit is contained in:
Lukas Müllner 2026-02-23 09:47:46 +01:00
parent 024c05734b
commit 7635b0d85b

View file

@ -212,42 +212,63 @@ class _ScannerScreenState extends State<ScannerScreen> {
/// Fallback: let the user type the HW ID manually. /// Fallback: let the user type the HW ID manually.
Future<void> _showManualEntry(BuildContext context) async { Future<void> _showManualEntry(BuildContext context) async {
final controller = TextEditingController();
final result = await showDialog<String>( final result = await showDialog<String>(
context: context, context: context,
builder: (ctx) => AlertDialog( builder: (_) => const _ManualEntryDialog(),
title: const Text('Enter HW ID'),
content: TextField(
controller: controller,
autofocus: true,
textCapitalization: TextCapitalization.characters,
decoration: const InputDecoration(
hintText: 'e.g. JKF21',
border: OutlineInputBorder(),
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () {
final value = controller.text.trim().toUpperCase();
if (value.isNotEmpty) Navigator.of(ctx).pop(value);
},
child: const Text('OK'),
),
],
),
); );
if (result != null && context.mounted) { if (result != null && context.mounted) {
controller.dispose();
Navigator.of(context).pop(result); Navigator.of(context).pop(result);
} else {
controller.dispose();
} }
} }
} }
// Manual Entry Dialog
class _ManualEntryDialog extends StatefulWidget {
const _ManualEntryDialog();
@override
State<_ManualEntryDialog> createState() => _ManualEntryDialogState();
}
class _ManualEntryDialogState extends State<_ManualEntryDialog> {
final _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _submit() {
final value = _controller.text.trim().toUpperCase();
if (value.isNotEmpty) Navigator.of(context).pop(value);
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('Enter HW ID'),
content: TextField(
controller: _controller,
autofocus: true,
textCapitalization: TextCapitalization.characters,
decoration: const InputDecoration(
hintText: 'e.g. JKF21',
border: OutlineInputBorder(),
),
onSubmitted: (_) => _submit(),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: _submit,
child: const Text('OK'),
),
],
);
}
}