← Flutter / Dart

Flutter / Dart · 10 / 18

Validate forms and navigate between screens

Validate input, dispose controllers and pass a confirmed value to a second screen.

  • Medium
  • Basics
  • 45 min

Prerequisites: Your first Flutter interface

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.

Example files

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.

main.dart · dartOpen file
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 chrome

02Validate 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.

Expected result

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.