---
title: "Detect unsaved changes in PDFs | Nutrient Flutter SDK"
canonical_url: "https://www.nutrient.io/guides/flutter/save-a-document/detect-unsaved-changes/"
md_url: "https://www.nutrient.io/guides/flutter/save-a-document/detect-unsaved-changes.md"
last_updated: "2026-05-30T02:20:01.297Z"
description: "Learn how to detect unsaved changes in PDF documents using Nutrient Flutter SDK. Track dirty state for annotations, bookmarks, and form fields across all platforms."
---

# Detecting unsaved changes in PDFs

Check if a document has any unsaved changes by calling `hasUnsavedChanges()` on a `PdfDocument` instance. This is useful for prompting users to save before closing a document or navigating away.

## Cross-platform detection

The `hasUnsavedChanges()` method works on all platforms (iOS, Android, and web):

```dart

void checkForChanges(PdfDocument document) async {
  final hasChanges = await document.hasUnsavedChanges();

  if (hasChanges) {
    // Prompt user to save or auto-save.
    await document.save();
  }
}

```

## Platform-specific methods

Each platform provides additional methods for more granular dirty state tracking.

### iOS

On iOS, check and manipulate the dirty state of individual annotations:

```dart

import 'dart:io';

void checkiOSDirtyState(PdfDocument document) async {
  if (!Platform.isIOS) return;

  // Check if document has any dirty annotations.
  final hasDirtyAnnotations = await document.iOSHasDirtyAnnotations();

  if (hasDirtyAnnotations) {
    // Check if a specific annotation is dirty.
    final isDirty = await document.iOSGetAnnotationIsDirty(
      0, // Page index.
      'annotation-uuid', // Annotation ID.
    );

    // Clear dirty state for a specific annotation.
    await document.iOSSetAnnotationIsDirty(0, 'annotation-uuid', false);

    // Or clear all dirty flags at once.
    await document.iOSClearNeedsSaveFlag();
  }
}

```

The following iOS methods are available:

| Method                                                      | Description                                           |
| ----------------------------------------------------------- | ----------------------------------------------------- |
| `iOSHasDirtyAnnotations()`                                  | Returns `true` if any annotation has unsaved changes. |
| `iOSGetAnnotationIsDirty(pageIndex, annotationId)`          | Returns the dirty state of a specific annotation.     |
| `iOSSetAnnotationIsDirty(pageIndex, annotationId, isDirty)` | Sets the dirty state of a specific annotation.        |
| `iOSClearNeedsSaveFlag()`                                   | Clears the needs-save flag on all annotations.        |

### Android

On Android, check dirty state separately for annotations, forms, and bookmarks:

```dart

import 'dart:io';

void checkAndroidDirtyState(PdfDocument document) async {
  if (!Platform.isAndroid) return;

  // Check each provider separately.
  final hasAnnotationChanges =
      await document.androidHasUnsavedAnnotationChanges();
  final hasFormChanges =
      await document.androidHasUnsavedFormChanges();
  final hasBookmarkChanges =
      await document.androidHasUnsavedBookmarkChanges();

  print('Unsaved changes:');
  print('  Annotations: $hasAnnotationChanges');
  print('  Forms: $hasFormChanges');
  print('  Bookmarks: $hasBookmarkChanges');

  // Check if a specific bookmark is dirty.
  final bookmarkDirty =
      await document.androidGetBookmarkIsDirty('bookmark-id');

  // Clear dirty state for a specific bookmark.
  await document.androidClearBookmarkDirtyState('bookmark-id');

  // Check if a specific form field is dirty.
  final fieldDirty =
      await document.androidGetFormFieldIsDirty('form.field.name');
}

```

The following Android methods are available:

| Method                                           | Description                                                    |
| ------------------------------------------------ | -------------------------------------------------------------- |
| `androidHasUnsavedAnnotationChanges()`           | Returns `true` if the annotation provider has unsaved changes. |
| `androidHasUnsavedFormChanges()`                 | Returns `true` if the form provider has unsaved changes.       |
| `androidHasUnsavedBookmarkChanges()`             | Returns `true` if the bookmark provider has unsaved changes.   |
| `androidGetBookmarkIsDirty(bookmarkId)`          | Returns the dirty state of a specific bookmark.                |
| `androidClearBookmarkDirtyState(bookmarkId)`     | Clears the dirty state of a specific bookmark.                 |
| `androidGetFormFieldIsDirty(fullyQualifiedName)` | Returns the dirty state of a specific form field.              |

### Web

On web, use the `webHasUnsavedChanges()` method:

```dart

import 'package:flutter/foundation.dart';

void checkWebDirtyState(PdfDocument document) async {
  if (!kIsWeb) return;

  final hasChanges = await document.webHasUnsavedChanges();
  if (hasChanges) {
    await document.save();
  }
}

```

## Example: Confirm before closing

The following example prompts the user before closing a document with unsaved changes. This example uses `PopScope`, which replaces `WillPopScope` (deprecated in Flutter 3.12):

```dart

import 'package:flutter/material.dart';
import 'package:nutrient_flutter/nutrient_flutter.dart';

class DocumentViewer extends StatefulWidget {
  final String documentPath;

  const DocumentViewer({super.key, required this.documentPath});

  @override
  State<DocumentViewer> createState() => _DocumentViewerState();
}

class _DocumentViewerState extends State<DocumentViewer> {
  PdfDocument? _document;
  bool _hasUnsavedChanges = false;

  Future<void> _checkForChanges() async {
    if (_document == null) return;
    final hasChanges = await _document!.hasUnsavedChanges();
    setState(() {
      _hasUnsavedChanges = hasChanges;
    });
  }

  Future<void> _handlePopInvoked(bool didPop) async {
    if (didPop) return;

    final shouldSave = await showDialog<bool>(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Unsaved Changes'),
        content: const Text(
          'You have unsaved changes. Do you want to save before leaving?',
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context, false),
            child: const Text('Discard'),
          ),
          TextButton(
            onPressed: () => Navigator.pop(context, true),
            child: const Text('Save'),
          ),
        ],
      ),
    );

    if (shouldSave == true) {
      await _document!.save();
    }

    if (context.mounted) {
      Navigator.pop(context);
    }
  }

  @override
  Widget build(BuildContext context) {
    return PopScope(
      canPop:!_hasUnsavedChanges,
      onPopInvokedWithResult: (didPop, result) => _handlePopInvoked(didPop),
      child: Scaffold(
        appBar: AppBar(title: const Text('Document')),
        body: NutrientView(
          documentPath: widget.documentPath,
          onDocumentLoaded: (document) {
            _document = document;
          },
          onDocumentSaved: (_) => _checkForChanges(),
        ),
      ),
    );
  }
}

```

The key differences from the older `WillPopScope` approach:

- `canPop` determines whether back navigation is allowed (set to `false` when there are unsaved changes)

- `onPopInvokedWithResult` is called when a pop is attempted — if `didPop` is `false`, the navigation was blocked and you can show a confirmation dialog

- You need to track changes proactively and update the `_hasUnsavedChanges` state

---

## Related pages

- [Auto save PDF files in Flutter](/guides/flutter/save-a-document.md)
- [Conflict resolution](/guides/flutter/save-a-document/conflict-resolution.md)
- [How to use Save As for PDFs in Flutter](/guides/flutter/save-a-document/save-as.md)
- [Save a document to a remote server in Flutter](/guides/flutter/save-a-document/save-to-remote.md)
- [Supported document save options in Flutter](/guides/flutter/save-a-document/save-options.md)

