Small details that build taste in Flutter.

curated by Kamran BekirovKamran Bekirov

Ask "Discard changes?" before going back

On edit forms, if the user goes back with unsaved changes, ask before throwing them away. One accidental back and everything they typed is gone:

Ask only when something changed. If nothing changed, just go back, a prompt there is annoying. Use the same comparison as in Disable the "Save" button until something changes, one _hasChanges flag powers both.

Name the buttons after what they do, "Discard changes" in red and "Keep editing". Don't use Yes / No / Cancel, the user can't tell what cancels what.

Intercept the back with PopScope:

PopScope(
  canPop: !_hasChanges,
  onPopInvokedWithResult: (bool didPop, Object? result) async {
    if (didPop) return;
  
    final bool? discard = await DiscardChangesDialog.show(context);
  
    if (discard == true && context.mounted) {
      Navigator.of(context).pop();
    }
  },
  child: Scaffold(...),
)

The dialog itself, style it to your app:

class DiscardChangesDialog {
  static Future<bool?> show(BuildContext context) {
    return showDialog<bool>(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: const Text('Discard changes?'),
          actions: [
            TextButton(
              onPressed: () {
                Navigator.of(context).pop(false);
              },
              child: const Text('Keep editing'),
            ),
            TextButton(
              onPressed: () {
                Navigator.of(context).pop(true);
              },
              child: const Text(
                'Discard changes',
                style: TextStyle(color: Colors.red),
              ),
            ),
          ],
        );
      },
    );
  }
}

Compose screens, like writing a post or an email, need the same guard, just with different buttons. There the user's work can be kept for later, so offer "Save draft" next to "Discard", like LinkedIn does. And if the app can save drafts by itself, do that and skip the prompt, like Gmail does. The check is simpler there too, in code just check that the field isn't empty.

Two things to know. A custom back button must call Navigator.maybePop, a plain pop skips PopScope. And on iOS the swipe back gesture turns off while canPop is false, the user goes through the back button, that's the price of the guard.

Kamran Bekirov
Kamran Bekirov

Want this level of care in your Flutter apps?

Work With Me