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.eventsstream, which uses the sealedNutrientEventtype. - Add custom native behavior through a typed controller. Register your adapter type with
Nutrient.addAdapterClass<T>()and select it withNutrientDocumentView<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'; |
NutrientView | NutrientDocumentView |
Nutrient.present(path) | NutrientDocumentView(documentPath: path) |
Nutrient.presentInstant(...) | NutrientInstantView |
PdfConfiguration(...) | NutrientViewConfiguration(...) |
onViewCreated: (controller) { ... } | onControllerReady: (controller) { ... } |
NutrientViewController | NutrientController |
onDocumentLoaded/onPageChanged/onPageClicked widget callbacks | controller.events (typed NutrientEvent stream) |
controller.addEventListener(NutrientEvent.annotationsCreated, ...) | controller.events.listen(...) over sealed events (AnnotationCreatedEvent, …) |
NutrientView(customToolbarItems:, onCustomToolbarItemTapped:) | controller.setMainToolbarItems([CustomToolbarItem(onPressed:)]) |
Headless PdfDocument | Nutrient.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 path | save(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(), andgeneratePdfFromHtmlUri()— 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 thepath_provider(opens in a new tab) package instead.Nutrient.analyticsEventsListener/enableAnalytics()— Legacy only.PdfConfiguration.defaultZoomScale,settingsMenuItems, andtoolbarTitle— Removed with no replacement.setAnnotationConfigurationswith the typed preset classes (InkAnnotationConfiguration,MarkupAnnotationConfiguration, …) — Not implemented on the bindings controller. Use the platform adapters for preset customization.- iOS
ThemeConfiguration/ToolbarTheme/AnnotationToolbarTheme— UseappearanceModefor light or dark mode, and use platform adapters for custom iOS colors. annotationToolsGroupingwithAnnotationToolbarItem/AnnotationToolsGroup— Replaced bycontroller.setAnnotationToolbarItems([...])withAnnotationToolandAnnotationToolGroup(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);// orfinal 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/NutrientViewmethod-channel API is marked@Deprecated. It still works, but new code should usepackage:nutrient_flutter/bindings.dart. Nutrient.frameworkVersionreports the underlying native SDK version on the current platform.