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(),
);
if (result != null && context.mounted) {
Navigator.of(context).pop(result);
}
}
}
// 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'), title: const Text('Enter HW ID'),
content: TextField( content: TextField(
controller: controller, controller: _controller,
autofocus: true, autofocus: true,
textCapitalization: TextCapitalization.characters, textCapitalization: TextCapitalization.characters,
decoration: const InputDecoration( decoration: const InputDecoration(
hintText: 'e.g. JKF21', hintText: 'e.g. JKF21',
border: OutlineInputBorder(), border: OutlineInputBorder(),
), ),
onSubmitted: (_) => _submit(),
), ),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.of(ctx).pop(), onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel'), child: const Text('Cancel'),
), ),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: _submit,
final value = controller.text.trim().toUpperCase();
if (value.isNotEmpty) Navigator.of(ctx).pop(value);
},
child: const Text('OK'), child: const Text('OK'),
), ),
], ],
),
); );
if (result != null && context.mounted) {
controller.dispose();
Navigator.of(context).pop(result);
} else {
controller.dispose();
}
} }
} }