This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /guides/flutter/platform-adapters/getting-started.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. Getting started with platform adapters

This guide shows you how to display a document with the cross-platform API and create a custom platform adapter when you need native access. To decide when to use an adapter, refer to the overview guide.

Prerequisites

Set up a working Nutrient Flutter project before you use platform adapters. For installation steps, supported SDK versions, and platform requirements, refer to the Flutter getting started guide.

Display a document

Most apps don’t need an adapter. Initialize the SDK once at startup, and then display a document with NutrientDocumentView. The widget builds the SDK’s default adapter and gives you a NutrientController in onControllerReady:

import 'package:flutter/material.dart';
import 'package:nutrient_flutter/bindings.dart';
class ViewerPage extends StatelessWidget {
final String documentPath;
const ViewerPage({super.key, required this.documentPath});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('PDF Viewer')),
body: NutrientDocumentView(
documentPath: documentPath,
onControllerReady: (controller) async {
final pageCount = await controller.document.getPageCount();
debugPrint('Loaded $pageCount pages');
controller.events.listen((event) => debugPrint('Event: $event'));
},
),
);
}
}

Initialize the SDK before you show a viewer. Nutrient.initialize() handles licensing only:

// Pass license keys to remove the trial watermark; omit them for trial mode.
await Nutrient.initialize(
androidLicenseKey: 'YOUR_ANDROID_KEY',
iosLicenseKey: 'YOUR_IOS_KEY',
webLicenseKey: 'YOUR_WEB_KEY',
);

Use controller.document for document operations, including annotations, bookmarks, forms, saving and exporting, and page information. Use controller.events for the typed cross-platform event stream. These APIs work the same way on Android, iOS, and Web. The view owns the controller and disposes it for you.

Create a custom adapter

Create a custom adapter when you need native access or controller methods that the cross-platform API doesn’t provide. Define a cross-platform controller interface, implement it for each platform, and register the controller type once. Each NutrientDocumentView<T> builds a new adapter for the view and disposes it for you.

Step one: Define the controller interface

The interface acts as the registry key and the widget’s type parameter. It extends NutrientController and doesn’t include platform imports:

lib/adapters/my_controller.dart
import 'package:nutrient_flutter/bindings.dart';
abstract class MyController implements NutrientController {
/// Page count read directly from the native document handle.
Future<int> nativePageCount();
}

Step two: Implement the adapter for each platform

Each implementation extends its platform base class, implements the shared interface, and reads the native handle inside the platform lifecycle hook.

lib/adapters/my_android_adapter.dart
import 'package:nutrient_flutter_android/nutrient_flutter_android.dart';
import 'my_controller.dart';
class MyAndroidAdapter extends AndroidAdapter implements MyController {
// Override `onFragmentReady` (not `onPdfFragmentReady`) — the base wires the
// event listeners and then calls this for your customization.
@override
Future<void> onFragmentReady(PdfFragment pdfFragment) async {
await super.onFragmentReady(pdfFragment);
// `nativePdfFragment`/`nativePdfDocument` are available here.
}
@override
Future<int> nativePageCount() async => nativePdfDocument?.getPageCount() ?? 0;
}

Step three: Add a conditional-import factory

The platform bindings can’t coexist in one build, so expose a single createMyAdapter() function through a conditional-import barrel. Your screens only import this barrel file:

lib/adapters/my_adapter.dart
export 'my_adapter_stub.dart'
if (dart.library.io) 'my_adapter_native.dart'
if (dart.library.js_interop) 'my_adapter_web.dart';
lib/adapters/my_adapter_native.dart
import 'dart:io' show Platform;
import 'my_android_adapter.dart';
import 'my_controller.dart';
import 'my_ios_adapter.dart';
MyController? createMyAdapter() {
if (Platform.isAndroid) return MyAndroidAdapter();
if (Platform.isIOS) return MyIOSAdapter();
return null;
}

The web factory (my_adapter_web.dart) returns MyWebAdapter(), and the stub (my_adapter_stub.dart) throws for unsupported targets. For the full file layout, refer to the platform imports guide.

Step four: Register the type and use it

Register the controller type once at startup. Then select it with the NutrientDocumentView type parameter:

main.dart
import 'package:flutter/material.dart';
import 'package:nutrient_flutter/bindings.dart';
import 'adapters/my_adapter.dart';
import 'adapters/my_controller.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Nutrient.initialize();
Nutrient.addAdapterClass<MyController>(() => createMyAdapter()!);
runApp(const MyApp());
}
NutrientDocumentView<MyController>(
documentPath: documentPath,
onControllerReady: (controller) async {
// `controller` is your `MyController` — the adapter is the controller.
final pages = await controller.nativePageCount();
debugPrint('Native page count: $pages');
},
)

If you use NutrientDocumentView<MyController> without registering MyController, the SDK throws a StateError that tells you to register the type or pass an adapter:.

Verify the installation

Run your app on each target platform to confirm that the adapter works:

Terminal window
# Android
flutter run -d android
# iOS
flutter run -d ios
# Web
flutter run -d chrome

The PDF viewer displays your document, and your adapter’s lifecycle hooks run on the matching platform.

Troubleshooting

Use this section to resolve common issues with platform imports and adapter registration.

Import errors when building for a different platform

If you see errors like Target of URI doesn’t exist or Dart library 'dart:io' is not available on this platform, an adapter file is importing bindings for the wrong platform. Keep each adapter in its own file and select between them with the conditional-import barrel. For more information, refer to the platform imports guide.

StateError about a missing adapter class

A NutrientDocumentView<MyController> throws a StateError when no builder is registered for that type. Call Nutrient.addAdapterClass<MyController>(() => createMyAdapter()!) once at startup, or pass a per-view adapter: instance instead.

Adapter lifecycle hooks aren’t called

Verify that the adapter uses the correct type for the current platform:

  • Android — Override onFragmentReady, not onPdfFragmentReady. The base class uses onPdfFragmentReady to wire the event stream.
  • iOS — Implement onViewControllerReady.
  • Web — Override onInstanceLoaded.

Next steps

Continue with these guides:

  • Refer to the usage patterns guide for native configuration, controller operations, and native events.
  • Refer to the platform imports guide to handle platform-specific imports.