Nutrient React Native SDK

repository·master·Indexed 42 days ago

https://github.com/pspdfkit/react-native

A high-fidelity PDF viewer and editor for React Native applications, providing tools for annotation, digital signatures, and form filling. Version 4.5.1 supports both the legacy Paper architecture and the new Fabric architecture via the NutrientView component.

Tokens
16K
Snippets
41
Records
59
Agent score
52%

What's inside Nutrient

  1. Overview of Nutrient React Native SDK

    master

    The Nutrient React Native SDK is a framework for viewing, annotating, signing, and filling PDF forms across iOS, Android, Windows (via legacy version), macOS, and Web. It also supports Nutrient Collaboration for real-time document sharing and editing.

    Important Platform Notes:

    • Windows Support: Current versions do not support Windows. If you require Windows support, use version 1.24.9.
    • Licensing: This is a commercial product. Using the library without a production license will trigger demo mode, which applies a watermark to PDFs and limits usage to 60 minutes.
  2. Special rules for PDFDocument APIs

    master

    When adding or changing methods on the PDFDocument TypeScript class (src/document/PDFDocument.ts), you must prioritize document-centric native modules over view-based modules to maintain architectural separation.

    Implementation Guidelines

    • iOS: Call into the PDFDocumentManager native module (ios/PDFDocumentManager.swift + PDFDocumentManager.m) instead of adding APIs to NutrientViewTurboModule or view managers.
    • Android: Mirror this behavior using the existing PDFDocumentModule.kt (android/src/main/java/com/pspdfkit/react/PDFDocumentModule.kt).
    • When to use View Modules: Only use NutrientView or view-backed TurboModules when the API acts on the view (e.g., UI state, toolbar, selection, visibility), not on the document model itself (e.g., page metadata, rotation, annotations, bookmarks).

    iOS Reloading Pattern

    If a document-centric change (like page rotation) requires the UI to update, use the PDFDocumentManager.swift delegate hook delegate?.reloadControllerData?() to trigger a reload of the active PDFViewController rather than accessing the view controller directly.

  3. How to bridge native APIs to TypeScript

    master

    When adding or changing any native API exposed to the TypeScript/JavaScript layer in the Nutrient React Native SDK, you must implement a dual path architecture to support both the Legacy and New Architecture (Fabric/TurboModules).

    Core Principles

    1. Dual Path: Every public API must branch on isNewArchitectureEnabled().
      • New Architecture: Uses TurboModule/Fabric references.
      • Legacy Architecture: Uses NativeModules, UIManager, or requireNativeComponent.
    2. Spec-first for New Architecture: All New Architecture APIs must be declared in a spec file under src/specs/ and implemented in the native directories (android/newarch or ios/Turbo/ or ios/Fabric/).
    3. Single Public Surface: The public API (typically index.js) acts as the single entry point, delegating calls to either the TurboModule/Fabric path or the Legacy path based on the detected architecture.

    Implementation Workflow

    • Step 0: Requirements Gathering: Before coding, identify the API feature, target platforms (iOS, Android, or both), module type (App-level TurboModule, View-backed TurboModule, or Fabric component), and exact TypeScript signatures.
    • Step 1: Implementation: Implement the logic in both native paths and the TypeScript spec.
    • Step 2: Verification: Run native builds (e.g., xcodebuild for iOS or ./gradlew assembleDebug for Android) using a consuming app like the included Catalog sample to ensure the bridge works and compiles.
  4. How to bridge a native API to the TypeScript layer

    master

    To expose a native API to the TypeScript/JavaScript layer in this library, you must implement a dual-path architecture that supports both Legacy (Paper) and New Architecture (Fabric + TurboModules).

    Core Rules

    1. Dual Path: Every public API must branch on isNewArchitectureEnabled(). It must delegate to either a TurboModule/Fabric ref (New Architecture) or NativeModules/UIManager (Legacy).
    2. Spec-First for New Architecture: New Architecture APIs must be declared in a spec under src/specs/ and implemented in native code (Android newarch, iOS Turbo/ or Fabric/).
    3. Single Public Surface: The public API (e.g., a class method or index.js export) must be the sole entry point for callers, handling the internal delegation to the appropriate architecture.

    API Module Types

    • TurboModule (App-level): For APIs with no view reference (e.g., license keys, present()). Spec: src/specs/NativeNutrientModule.ts.
    • View-backed TurboModule: For APIs operating on a specific NutrientView instance (e.g., setPageIndex). Spec: src/specs/NativeNutrientViewTurboModule.ts.
    • Fabric Component: For rendering, props, and events related to the view. Spec: src/specs/NutrientViewNativeComponent.ts.
    // Pattern for the Public API layer
    methodName = function (arg1, arg2) {
      const { isNewArchitectureEnabled } = require('./lib/ArchitectureDetector');
      if (isNewArchitectureEnabled()) {
        // New Architecture: TurboModule or Fabric ref
        return this._fabricRef.current?.methodName(arg1, arg2);
        // OR for app-level: return NativeNutrientModule.methodName(arg1, arg2);
      }
      // Legacy: NativeModules / UIManager
      if (Platform.OS === 'android') {
        return NativeModules.ModuleName.methodName(/* ... */);
      } else if (Platform.OS === 'ios') {
        return NativeModules.ModuleName.methodName(/* ... */);
      }
    };
  5. License and Demo Mode behavior

    master

    The Nutrient React Native SDK requires a valid license for production use. Licenses are issued per platform.

    If no valid license is provided during initialization:

    • The library operates in demo mode.
    • A watermark will be placed on every PDF.
    • Usage is limited to 60 minutes.
  6. How the PSPDFKit Fabric implementation works

    master

    The Fabric implementation acts as a bridge between React Native's New Architecture and the existing PSPDFKit view implementation. It allows the library to support both the legacy Paper architecture and the new Fabric architecture simultaneously.

    Core Workflow

    1. Component Registration: The component is automatically registered via React Native's codegen system using the codegenConfig defined in package.json.
    2. Props Handling: Props are passed as C++ types. For complex configurations, JSON passthrough props (such as configurationJSONString and toolbarJSONString) are parsed and applied using helper classes.
    3. View Rendering: The Fabric component view manages an instance of RCTPSPDFKitView, which is responsible for all PDF rendering.
    4. Event Handling: Events are emitted through a codegen-generated EventEmitter interface and forwarded to the JavaScript layer.

    Architecture Compatibility

    • Paper (Legacy): Uses RCTPSPDFKitView via the legacy ViewManager.
    • Fabric (New): Uses the NutrientView codegen component, which wraps RCTPSPDFKitView while providing Fabric-compatible props and events.
  7. Quickstart: Integrate NutrientView into a React Native app

    master

    To display a PDF using NutrientView, follow these steps:

    1. Android Setup: Ensure mavenCentral() is in your android/build.gradle repositories. Set compileSdkVersion to 36 and minSdkVersion to 24 in android/app/build.gradle. Place your PDF in android/app/src/main/assets/.
    2. iOS Setup: Set the minimum iOS platform version to '17.0' in your Podfile. In Info.plist, set View controller-based status bar appearance to YES. Drag your PDF into the Xcode project.
    3. Implementation: Use the NutrientView component, providing a document path (using file:///android_asset/ for Android) and a configuration object.
    import React, { Component } from 'react';
    import { Platform } from 'react-native';
    import NutrientView from '@nutrient-sdk/react-native';
    import { NativeModules } from 'react-native';
    
    const Nutrient = NativeModules.Nutrient;
    // Note: Passing null as a license key is used for demo mode in this example
    Nutrient.setLicenseKey(null);
    
    const DOCUMENT =
      Platform.OS === 'ios'
        ? 'Document.pdf'
        : 'file:///android_asset/Document.pdf';
    
    export default class NutrientDemo extends Component<{}> {
      render() {
        const pdfRef: React.RefObject<NutrientView | null> = React.createRef();
        return (
          <NutrientView
            document={DOCUMENT}
            configuration={{
              showThumbnailBar: 'scrollable',
              pageTransition: 'scrollContinuous',
              scrollDirection: 'vertical',
            }}
            ref={pdfRef}
            fragmentTag="PDF1"
            style={{ flex: 1 }}
          />
        );
      }
    }
  8. Prompt an AI assistant to bridge a Native API

    master

    When using an AI coding agent (like Cursor or others) to implement new native-backed APIs in the Nutrient React Native SDK, do not use free-form descriptions. Instead, use a structured prompt that forces the agent to follow the specific recipe defined in BRIDGING.md. This ensures the agent follows the correct steps for both Legacy and New Architecture and doesn't skip critical implementation details.

    To use this method:

    1. Provide context: Explicitly point the AI to @BRIDGING.md so it loads the recipe first.
    2. Use the structured prompt: Copy the template below and fill in all bracketed information regarding the API requirements, platform, module type, and method signatures.
    You MUST follow the recipe in BRIDGING.md step by step. Do not skip steps or use a different pattern. Read that file first. The user has already provided the requirements below; do not ask again—proceed to Step 1 of the recipe.
    
    Requirements:
    
    - **API / feature:** [e.g. "a new TurboModule method getDocumentMetadata that returns title and page count"]
    - **Platform:** [iOS only / Android only / both]
    - **Module:** [App-level TurboModule (NativeNutrientModule) / View-backed TurboModule (NativeNutrientViewTurboModule) / Fabric component]
    - **Method name(s) and signatures:** [e.g. getDocumentMetadata(): Promise<{ title: string, pageCount: number }>
    - **Links to iOS/Android APIs or guides:** [e.g. "https://developer.apple.com/documentation/...", "https://developer.android.com/reference/...", or SDK guide URLs]
    - **Extra context:** [e.g. "already implemented in iOS in MyCustomModule.mm" or "none"]
    
    After implementing:
    1. Use the "Checklist before submitting" in BRIDGING.md and confirm each item.
    2. Ensure changes exist both in the local SDK and (for testing) in the app's node_modules/@nutrient-sdk/react-native.
    3. After the user confirms they are happy with the result, run the create-bridge-patch script so they can commit the patch.
  9. Verify bridge changes with the Catalog sample app

    master

    To verify that your native bridge changes work correctly, you should run the builds for the consuming app (the Catalog sample app).

    Verification Steps

    1. Sync Native Changes: Ensure the sample app's node_modules/@nutrient-sdk/react-native reflects your changes. You can do this by running npm run dev-build from the SDK root or by manually mirroring edits into the Catalog app's node_modules copy.
    2. Run iOS Build: Use xcodebuild from the sample app directory.
    3. Run Android Build: Use ./gradlew assembleDebug from the sample app directory.

    If the build fails, resolve the native compilation errors and re-run the build process.

    # From the SDK root
    npm run dev-build
    
    # From the sample app directory (Android)
    ./gradlew assembleDebug
  10. Create a bridge patch using patch-package

    master

    Once native changes have been implemented and tested, you must create a patch so that the changes can be committed to source control and reliably reapplied after an npm install.

    Do not run patch-package steps manually. Instead, use the provided script to ensure the patch is created correctly for the @nutrient-sdk/react-native package.

    From the App Root

    If you are working within the customer's application directory:

    node node_modules/@nutrient-sdk/react-native/scripts/create-bridge-patch.js

    From the SDK Repository

    If you are working within the SDK repository (e.g., testing against the Catalog app):

    npm run create-bridge-patch -- samples/Catalog
    # or
    node scripts/create-bridge-patch.js samples/Catalog

    The script will ensure patch-package is configured and create a patch file in the patches/ directory. You must then commit the patches/ directory to your version control system.

    node node_modules/@nutrient-sdk/react-native/scripts/create-bridge-patch.js
    # or from SDK root
    npm run create-bridge-patch -- samples/Catalog
  11. Validate native changes with iOS and Android builds

    master

    After copying native edits into samples/Catalog/node_modules/@nutrient-sdk/react-native/, you must verify that the changes compile correctly by running the full native builds for both platforms.

    iOS Build

    Run the following from the project root:

    cd samples/Catalog/ios && pod install && xcodebuild -workspace Catalog.xcworkspace -scheme Catalog -configuration Debug -sdk iphonesimulator build

    Android Build

    Run the following from the project root:

    cd samples/Catalog/android && ./gradlew assembleDebug

    If a build fails, you must fix the issue in the SDK root, sync those changes to node_modules/@nutrient-sdk/react-native, and re-run the build until both platforms succeed.

    cd samples/Catalog/ios && pod install && xcodebuild -workspace Catalog.xcworkspace -scheme Catalog -configuration Debug -sdk iphonesimulator build
    cd samples/Catalog/android && ./gradlew assembleDebug
  12. Recipe for adding or changing a native-backed API

    master

    Follow these steps to ensure a new or changed native API is correctly implemented for both architectures.

    Step 0: Gather Requirements

    Before writing code, identify:

    1. API/Feature: Exact functionality.
    2. Platform: iOS, Android, or both.
    3. Module Type: App-level TurboModule, View-backed TurboModule, or Fabric component.
    4. Signatures: Exact names and TypeScript signatures.
    5. Native Docs: Links to iOS/Android API references.
    6. Context: Existing implementations or constraints.

    Step 1-2: Define Shape and New Architecture Spec

    • Decide if the API is App-level, View-backed, or Fabric.
    • Create/update the spec in src/specs/ (e.g., NativeNutrientModule.ts).
    • Use EventEmitter<Payload> from react-native/Libraries/Types/CodegenTypes for events.
    • Export the spec using TurboModuleRegistry.getEnforcing<Spec>('NativeModuleName').

    Step 3-4: Native Implementation

    • New Architecture (Android): Implement in android/src/newarch/java/.
    • New Architecture (iOS): Implement in ios/Turbo/.
    • Legacy (Android): Implement in android/src/main/java/.
    • Legacy (iOS): Implement in the appropriate Legacy module (e.g., RCTPSPDFKitViewManager). Ensure all selectors are declared in the corresponding *.h files to avoid compile errors.

    Step 5-7: Public API and Types

    • Public API: Implement the dual-branch logic using isNewArchitectureEnabled() from ./lib/ArchitectureDetector.
    • Types: If the API is documented in index.js, run npm run generate-types to sync types/index.d.ts.