An input field is not yet persisted domain information. The interface first needs a draft. It then validates input and passes a confirmed value. Our form requires a vehicle name and then navigates to a preview. This preview does not persist a vehicle record. The deliberately small example separates input, validation and navigation.
01Use the learning project
Use the Flutter project from tutorial 09 and replace lib/main.dart. Form groups form fields. A GlobalKey lets us trigger validation from outside the field. A TextEditingController holds current text.
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: VehicleForm()));
class VehicleForm extends StatefulWidget {
const VehicleForm({super.key});
@override
State<VehicleForm> createState() => _VehicleFormState();
}
class _VehicleFormState extends State<VehicleForm> {
final form = GlobalKey<FormState>();
final name = TextEditingController();
@override
void dispose() {
name.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text('New vehicle')),
body: Padding(
padding: const EdgeInsets.all(24),
child: Form(
key: form,
child: Column(
children: [
TextFormField(
controller: name,
decoration: const InputDecoration(labelText: 'Name'),
validator: (value) =>
(value ?? '').trim().isEmpty ? 'Name required' : null,
),
FilledButton(
onPressed: () {
if (!form.currentState!.validate()) return;
final title = name.text.trim();
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => Scaffold(
appBar: AppBar(title: const Text('Preview')),
body: Center(child: Text(title)),
),
),
);
},
child: const Text('Preview'),
),
],
),
),
),
);
}
Run / check
flutter run -d chrome02Validate a clear rule
validator returns error text for invalid input and null for valid input. trim removes outer whitespace. The submit button calls validate before navigation. The real app also checks domain rules in its service.
03Understand navigation and lifecycle
Navigator.push places a route on the navigation stack. The second screen receives an already normalised string. Back navigation removes the route. dispose releases the controller when the form is permanently removed.
04Actually test empty inputs
Test empty input, whitespace-only input and a name with surrounding whitespace. Only the last case should reach the preview. Restarting removes the name because we do not yet use a database.
Empty input displays Name required. Family car opens a preview.
Your exercise
Limit the name to 60 characters. Check lengths 60 and 61.
Show the solution and explanation
Normalise first. Then check isEmpty and length > 60 in validator. Return different error messages for the two cases. The preview still uses the same normalised value.
Project context and sources
The example is simplified for learning. The linked source version shows its use in the project and the checks applied there.