This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /guides/flutter/migration-guides/flutter-6-migration-guide.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. Flutter SDK 6 migration guide

Nutrient Flutter SDK 6 uses generated native bindings. The new bindings API drives each platform’s native SDK directly — Java Native Interface (JNI) on Android, Foreign Function Interface (FFI) on iOS, and JavaScript (JS) interop on the web — instead of routing every call through a Pigeon method channel. Use it for new work.

The legacy method-channel API (Nutrient.present(), NutrientView, PdfConfiguration, PdfDocument) is deprecated but not removed. Existing apps keep working without changes, and you can migrate one screen at a time.

What’s new in the bindings API

Nutrient Flutter SDK 6 adds these bindings API changes:

  • Use a single import, package:nutrient_flutter/bindings.dart, for the bindings surface.
  • Use the built-in default adapter for a standard viewer. You only need await Nutrient.initialize() — no license key or adapter required.
  • Use NutrientDocumentView, a widget backed by a per-platform adapter that also acts as its controller.
  • Listen to typed events with the controller.events stream, which uses the sealed NutrientEvent type.
  • Add custom native behavior through a typed controller. Register your adapter type with Nutrient.addAdapterClass<T>() and select it with NutrientDocumentView<T>.
  • Access native objects through platform adapters for capabilities not yet available on the cross-platform API.

Map legacy APIs to the bindings API

Each legacy API in package:nutrient_flutter/nutrient_flutter.dart has a bindings equivalent in package:nutrient_flutter/bindings.dart:

Legacy (method-channel API)Bindings API
import 'package:nutrient_flutter/nutrient_flutter.dart';import 'package:nutrient_flutter/bindings.dart';
NutrientViewNutrientDocumentView
Nutrient.present(path)NutrientDocumentView(documentPath: path)
Nutrient.presentInstant(...)NutrientInstantView
PdfConfiguration(...)NutrientViewConfiguration(...)
onViewCreated: (controller) { ... }onControllerReady: (controller) { ... }
NutrientViewControllerNutrientController
onDocumentLoaded/onPageChanged/onPageClicked widget callbackscontroller.events (typed NutrientEvent stream)
controller.addEventListener(NutrientEvent.annotationsCreated, ...)controller.events.listen(...) over sealed events (AnnotationCreatedEvent, …)
NutrientView(customToolbarItems:, onCustomToolbarItemTapped:)controller.setMainToolbarItems([CustomToolbarItem(onPressed:)])
Headless PdfDocumentNutrient.openDocument(path)/controller.document

The following sections cover the most common conversions.

Update annotation and form APIs

Document, annotation, and form operations moved from PdfDocument to the loaded document’s controller: controller.document, controller.document.annotations, and controller.document.forms. Access them from NutrientDocumentView in onControllerReady.

Several methods also changed shape:

Legacy (PdfDocument)Bindings
getAnnotations(page, type)controller.document.annotations.getAnnotations(page) — returns typed Annotation models; the type argument is now optional and defaults to all types
addAnnotation(annotation)controller.document.annotations.addAnnotation(annotation)
addAnnotations([...])Removed — call addAnnotation in a loop
removeAnnotation(annotation)controller.document.annotations.removeAnnotation(pageIndex, annotationId)
importXfdf(path)controller.document.annotations.importXfdf(xfdfString) — takes the XFDF (XML Forms Data Format) content, not a file path
exportXfdf(path)controller.document.annotations.exportXfdf()returns the XFDF as a string (no path argument)
applyInstantJson(json) / exportInstantJson()controller.document.applyInstantJson(json) / exportInstantJson()
processAnnotations(AnnotationType.all, mode, path)Nutrient.processAnnotations(sourcePath:, type: AnnotationType.all, mode: AnnotationProcessingMode.flatten, destinationPath:) — a static operation; no open document required (Android and iOS)
getFormFieldValue(name) / setFormFieldValue(v, name)controller.document.forms.getFormFieldValue(name) / setFormFieldValue(v, name)
getFormFields()Moved to the forms manager: controller.document.forms.getFormFields()

For the full annotation and form APIs, refer to the annotations and forms guides.

Update configuration

NutrientViewConfiguration replaces PdfConfiguration. Cross-platform options keep the same names. Platform-prefixed options move into nested platform blocks:

Legacy (PdfConfiguration)Bindings (NutrientViewConfiguration)
iOS*-prefixed options (iOSRightBarButtonItems, …)iosConfig: IOSViewConfiguration(rightBarButtonItems: ...)
android*-prefixed options (androidShowSearchAction, …)androidConfig: AndroidViewConfiguration(showSearchAction: ...)
webConfiguration: PdfWebConfiguration(...)webConfig: WebViewConfiguration(...)
iOSFileConflictResolution:iosConfig: IOSViewConfiguration(fileConflictResolution: ...)
androidDefaultThemeResource: / androidDarkThemeResource:androidConfig: AndroidViewConfiguration(defaultThemeResource: ..., darkThemeResource: ...)
signatureSavingStrategy:/signatureCreationConfiguration:Same names — carried over, and the creation configuration now also applies on Web and adds a fonts option

Update document saving

The bindings API moves document saving and bookmark operations to controller.document:

Legacy (PdfDocument)Bindings
Save As via processAnnotations('all', 'embed', path)controller.document.save(outputPath: path) on Android and iOS
save() on Web with a pathsave(outputPath:) throws UnsupportedError on Web — use exportPdf() for the browser download flow
hasUnsavedChanges()Same name — now on controller.document
Granular dirty-state methods (iOSGetAnnotationIsDirty(), androidGetBookmarkIsDirty(), …)Not bridged — use hasUnsavedChanges(), event-based tracking, or the platform adapters
getBookmarks()/addBookmark()/removeBookmark()/updateBookmark()Moved to the bookmark manager: controller.document.bookmarks.*

Review APIs without a bindings equivalent

The following legacy APIs have no counterpart on the bindings surface. They continue to work on the deprecated method-channel API. Where noted, use platform adapters as the extension point:

  • Nutrient.generatePdf(), generatePdfFromHtmlString(), and generatePdfFromHtmlUri() — Refer to the [PDF generation][] guide. PDF generation stays on the legacy API for now. Nutrient plans to add a bindings equivalent. In the meantime, reach the native processors through the platform adapters.
  • Nutrient.getTemporaryDirectory() — Use the path_provider(opens in a new tab) package instead.
  • Nutrient.analyticsEventsListener/enableAnalytics() — Legacy only.
  • PdfConfiguration.defaultZoomScale, settingsMenuItems, and toolbarTitle — Removed with no replacement.
  • setAnnotationConfigurations with the typed preset classes (InkAnnotationConfiguration, MarkupAnnotationConfiguration, …) — Not implemented on the bindings controller. Use the platform adapters for preset customization.
  • iOS ThemeConfiguration/ToolbarTheme/AnnotationToolbarTheme — Use appearanceMode for light or dark mode, and use platform adapters for custom iOS colors.
  • annotationToolsGrouping with AnnotationToolbarItem/AnnotationToolsGroup — Replaced by controller.setAnnotationToolbarItems([...]) with AnnotationTool and AnnotationToolGroup(representative:, items:).

Make required changes for every app

These changes apply to all apps that upgrade to SDK 6, including apps that stay on the legacy API.

Change AnnotationProperties color fields to int?

AnnotationProperties.strokeColor and AnnotationProperties.fillColor changed from String? (hex) to int? (ARGB — alpha, red, green, blue). This aligns the type with how withColor, withFillColor, and the color / fillColorValue getters already worked.

// Before.
final props = AnnotationProperties(strokeColor: '#FF0000');
// After.
final props = AnnotationProperties(strokeColor: 0xFFFF0000);
// or
final props = AnnotationProperties(strokeColor: Colors.red.toARGB32());

Set the iOS deployment target to 17

Set your iOS deployment target to 17.0 or higher in Xcode and your Podfile:

platform :ios, '16.0'
platform :ios, '17.0'

Stop importing Pigeon-generated classes

package:nutrient_flutter/nutrient_flutter.dart no longer reexports the Pigeon-generated *Api/*Callbacks proxy classes. Nutrient never intended these classes as public APIs. If you imported them directly, switch to the public PdfDocument/NutrientViewController interfaces instead.

Remove Pspdfkit.useLegacy

SDK 6 removes the legacy MethodChannel bridge. All cross-platform communication now goes through the Pigeon-generated APIs and the platform-adapter bindings, so the useLegacy flag no longer exists. It only selected the now-removed method-channel widget controller.

If you set useLegacy, remove the argument. It has no replacement, and the Pigeon-backed controller is always used.

// Before.
await Pspdfkit.initialize(androidLicenseKey: key, useLegacy: true);
// After.
await Pspdfkit.initialize(androidLicenseKey: key);

The deprecated Nutrient.present()/NutrientView Dart APIs are unaffected. They now route through Pigeon and keep working.

Adopt the bindings API

Migration is optional and incremental. The steps below convert one screen to the bindings API. Screens that still use NutrientView or Nutrient.present() keep working alongside it.

1. Add the federated packages

The bindings API is split across federated packages that must be listed as direct dependencies so their native plugins register at runtime. Refer to the [getting started][] guide for the full dependency and platform setup. Then return here to convert your screens.

2. Switch the import

Replace the legacy barrel with the bindings library.

// Before.
import 'package:nutrient_flutter/nutrient_flutter.dart';
// After.
import 'package:nutrient_flutter/bindings.dart';

Keep import 'package:nutrient_flutter/nutrient_flutter.dart'; in any file that still uses the legacy API. The two libraries are independent.

3. Initialize the SDK and present a document

The bindings API registers a built-in default adapter, so initialization no longer requires arguments for a standard viewer. Replace NutrientView/Nutrient.present() with NutrientDocumentView.

// Before — legacy method-channel API.
await Nutrient.initialize(androidLicenseKey: key, iosLicenseKey: key);
NutrientView(documentPath: path);
// After — bindings API.
await Nutrient.initialize(); // Pass license keys to remove the trial watermark.
NutrientDocumentView(documentPath: path);

NutrientDocumentView accepts an asset path (assets/doc.pdf), a file path, or a remote URL and resolves asset paths internally.

4. Move event handling to controller.events

NutrientDocumentView exposes a typed controller through onControllerReady. A single controller.events stream replaces the legacy per-widget callbacks (onDocumentLoaded, onPageChanged, …) and the enum-keyed controller.addEventListener(...). controller.document handles document operations.

// Before — per-widget callbacks + enum-keyed listeners.
NutrientView(
documentPath: path,
onDocumentLoaded: (document) => print('Loaded'),
onPageChanged: (pageIndex) => print('Page: $pageIndex'),
onViewCreated: (controller) {
controller.addEventListener(
NutrientEvent.annotationsCreated,
(event) => print('Annotation created'),
);
},
);
// After — one typed stream on the controller.
NutrientDocumentView(
documentPath: path,
onControllerReady: (controller) async {
final pageCount = await controller.document.getPageCount();
// Filtered subscription.
controller.events.pageChanged.listen((e) => print('Page ${e.pageIndex}'));
// Or handle the sealed `NutrientEvent` type exhaustively.
controller.events.listen((event) {
switch (event) {
case PageChangedEvent(:final pageIndex):
print('Page: $pageIndex');
case DocumentLoadedEvent():
print('Loaded');
default:
break;
}
});
},
);

Platform-specific events are available on the platform adapter: androidEvents on AndroidAdapter, iosEvents on IOSAdapter, and webEvents on NutrientWebAdapter.

5. Update the Android launcher activity

Bindings apps host the viewer in NutrientFlutterActivity, which nutrient_flutter_android provides, rather than a custom MainActivity or FlutterAppCompatActivity. Point the launcher activity at it and enable trial-mode auto-initialization in AndroidManifest.xml:

<activity
android:name=".MainActivity"
android:name="com.nutrient.nutrient_flutter_android.NutrientFlutterActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
...>
...
</activity>
<meta-data
android:name="nutrient_automatic_initialize"
android:value="true"
tools:replace="android:value" />

The viewer requires an AppCompat-descendant theme, so keep your LaunchTheme/NormalTheme on a PSPDFKit.Theme* parent. The nutrient_automatic_initialize metadata is required because the bindings API initializes the SDK through NutrientDocumentView rather than the method channel.

Customize the viewer with platform adapters

You don’t need an adapter for the default viewer. To customize the viewer’s configuration, listen to native lifecycle events, or reach platform-specific APIs, subclass the platform adapter (AndroidAdapter, IOSAdapter, or NutrientWebAdapter). The adapter is the controller.

Register your controller type once with Nutrient.addAdapterClass<T>() and select it with NutrientDocumentView<T>, or pass a per-view adapter: instance you own. Adapters also expose accessors to the underlying native objects — the native PdfFragment, PSPDFViewController, or web Instance, plus form providers — for advanced features.

For details, refer to the [platform adapters][] guide.

Review deprecations

Nutrient Flutter SDK 6 includes these deprecations and version-reporting changes:

  • The legacy Nutrient/NutrientView method-channel API is marked @Deprecated. It still works, but new code should use package:nutrient_flutter/bindings.dart.
  • Nutrient.frameworkVersion reports the underlying native SDK version on the current platform.