---
title: "Platform imports"
canonical_url: "https://www.nutrient.io/guides/flutter/platform-adapters/platform-imports/"
md_url: "https://www.nutrient.io/guides/flutter/platform-adapters/platform-imports.md"
last_updated: "2026-05-20T19:49:34.815Z"
description: "Learn how to handle platform-specific imports for Android, iOS, and Web when using platform adapters in Nutrient Flutter SDK."
---

Platform adapters give you direct access to native SDKs, but each platform uses different language bindings — JNI on Android, Objective-C FFI on iOS, and JavaScript interop on Web. These bindings cannot coexist in a single compilation target. If your Android adapter imports JNI types, that import will fail when building for iOS or Web, and vice versa.

This guide explains how to structure your code so that only the correct platform’s bindings are included in each build.

## The problem

Each platform adapter imports bindings that only exist on its target platform:

| Platform | Binding type    | Import                                                                             |
| -------- | --------------- | ---------------------------------------------------------------------------------- |
| Android  | JNI             | `package:nutrient_flutter_android/src/bindings/nutrient_android_sdk_bindings.dart` |
| iOS      | Objective-C FFI | `package:nutrient_flutter_ios/src/bindings/nutrient_ios_bindings.dart`             |
| Web      | JS interop      | `package:nutrient_flutter_web/nutrient_flutter_web.dart`                           |

These imports are mutually exclusive. The JNI package doesn’t exist when compiling for iOS, the Objective-C package doesn’t exist when compiling for Android, and `dart:js_interop` is only available on Web. If you import all three adapters in the same file, the build will fail on every platform.

The solution is to isolate each adapter in its own file and use a factory pattern to select the correct one at build time.

## Platform checks (Android and iOS only)

If your app only targets Android and iOS, the simplest approach is runtime platform checks with `dart:io`. Each adapter lives in its own file, so the Dart compiler never encounters bindings for the wrong platform:

```dart

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

// Each file only imports its own platform's bindings.
import 'adapters/my_android_adapter.dart';
import 'adapters/my_ios_adapter.dart';

NutrientPlatformAdapter _createAdapter() {
  if (Platform.isAndroid) {
    return MyAndroidAdapter();
  } else if (Platform.isIOS) {
    return MyIOSAdapter();
  }
  throw UnsupportedError('Platform not supported');
}

class PdfViewerPage extends StatelessWidget {
  final String documentPath;

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

  @override
  Widget build(BuildContext context) {
    return NutrientView(
      documentPath: documentPath,
      adapter: _createAdapter(),
    );
  }
}

```

This works because `my_android_adapter.dart` only imports JNI types, and `my_ios_adapter.dart` only imports Objective-C types. The Dart compiler tree-shakes the unused platform at build time.

**Important:** This approach doesn’t work when targeting Web. The `dart:io` library isn’t available on the web platform. If you need to support Web, use the conditional imports approach below.

## Conditional imports (recommended)

When your app targets Android, iOS, and Web, use Dart’s conditional imports to resolve the correct adapter at compile time. This is the standard Dart mechanism for platform-specific code.

### How it works

Dart’s conditional export syntax selects which file to include based on the availability of platform libraries:

```dart

export 'adapters_stub.dart'
    if (dart.library.io) 'adapters_native.dart'
    if (dart.library.js_interop) 'adapters_web.dart';

```

The Dart compiler evaluates these conditions at build time:

- **`dart.library.io`** is available on Android and iOS — selects `adapters_native.dart`.

- **`dart.library.js_interop`** is available on Web — selects `adapters_web.dart`.

- If neither is available, the stub fallback is used.

Only one file is included per build. The other files and their platform-specific imports are never compiled.

### File structure

```text

lib/
├── main.dart
└── adapters/
    ├── adapters.dart           # Entry point with conditional exports

    ├── adapters_stub.dart      # Fallback for unsupported platforms

    ├── adapters_native.dart    # Android/iOS factory (uses dart:io)

    ├── adapters_web.dart       # Web factory

    ├── my_controller.dart      # Shared controller interface (no platform imports)

    ├── my_android_adapter.dart # Android adapter (imports JNI bindings)

    ├── my_ios_adapter.dart     # iOS adapter (imports ObjC bindings)

    └── my_web_adapter.dart     # Web adapter (imports JS interop)

```

Each platform adapter file only imports its own platform’s bindings. The factory files create the correct adapter and return it as the shared controller interface type.

### Step 1: Entry point

The entry point file reexports the controller interface and conditionally exports the correct factory function:

```dart

// lib/adapters/adapters.dart
library;

export 'my_controller.dart';
export 'adapters_stub.dart'
    if (dart.library.io) 'adapters_native.dart'
    if (dart.library.js_interop) 'adapters_web.dart';

```

Consumers import this single file. The conditional export resolves to the correct factory at compile time.

### Step 2: Stub fallback

The stub provides a default implementation for unsupported platforms. It defines the same `createAdapter()` function signature as the other factory files, but returns `null`:

```dart

// lib/adapters/adapters_stub.dart
import 'my_controller.dart';

MyController? createAdapter({
  StatusChangedCallback? onStatusChanged,
  DocumentInfoCallback? onDocumentInfo,
}) {
  return null;
}

```

### Step 3: Native factory (Android and iOS)

This file is selected when `dart.library.io` is available. It uses `dart:io` for runtime platform detection and creates the correct adapter:

```dart

// lib/adapters/adapters_native.dart
import 'dart:io';

import 'my_android_adapter.dart';
import 'my_ios_adapter.dart';
import 'my_controller.dart';

MyController? createAdapter({
  StatusChangedCallback? onStatusChanged,
  DocumentInfoCallback? onDocumentInfo,
}) {
  if (Platform.isAndroid) {
    return MyAndroidAdapter(
      onStatusChanged: onStatusChanged,
      onDocumentInfo: onDocumentInfo,
    );
  } else if (Platform.isIOS) {
    return MyIOSAdapter(
      onStatusChanged: onStatusChanged,
      onDocumentInfo: onDocumentInfo,
    );
  }
  return null;
}

```

### Step 4: Web factory

This file is selected when `dart.library.js_interop` is available:

```dart

// lib/adapters/adapters_web.dart
import 'my_web_adapter.dart';
import 'my_controller.dart';

MyController? createAdapter({
  StatusChangedCallback? onStatusChanged,
  DocumentInfoCallback? onDocumentInfo,
}) {
  return MyWebAdapter(
    onStatusChanged: onStatusChanged,
    onDocumentInfo: onDocumentInfo,
  );
}

```

### Step 5: Usage

Import the entry point file. The `createAdapter()` function is available regardless of platform — Dart resolves it to the correct factory at compile time:

```dart

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

// Conditional import resolves to the correct
// platform factory.
import 'adapters/adapters.dart';

class PdfViewerPage extends StatelessWidget {
  final String documentPath;

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

  @override
  Widget build(BuildContext context) {
    final controller = createAdapter();
    return NutrientView(
      documentPath: documentPath,
      adapter: controller as NutrientPlatformAdapter?,
    );
  }
}

```

All three factory files define `createAdapter()` with the same signature, so the calling code works identically on every platform.

## Key points

Keep the following principles in mind when structuring platform-specific code:

- Keep each platform adapter in its own file to isolate platform-specific imports.

- The shared controller interface and callback types contain no platform imports.

- All factory files define the same function signature so the calling code is platform-agnostic.

- The `createAdapter()` factory returns `null` on unsupported platforms — handle this in your UI.

- For native-only apps, `dart:io` platform checks are sufficient. Add conditional imports when you also target Web.

## Next steps

- [Usage patterns](https://www.nutrient.io/guides/flutter/platform-adapters/usage.md) — See full examples
---

## Related pages

- [Android](/guides/flutter/platform-adapters/getting-started.md)
- [Platform Adapters](/guides/flutter/platform-adapters.md)
- [Native Sdk Reference](/guides/flutter/platform-adapters/native-sdk-reference.md)
- [Usage](/guides/flutter/platform-adapters/usage.md)

