---
title: "Save PDF to custom data provider on Android | Nutrient SDK"
canonical_url: "https://www.nutrient.io/guides/android/save-a-document/to-custom-data-provider/"
md_url: "https://www.nutrient.io/guides/android/save-a-document/to-custom-data-provider.md"
last_updated: "2026-06-09T10:26:34.448Z"
description: "Nutrient supports loading data from many different sources. In fact, this can be done from any object that conforms to the DataProviding protocol."
---

# Save PDFs to a custom data provider on Android

Nutrient supports loading data from many different sources. In fact, this can be done from any object that conforms to the `DataProviding` protocol, which is known as a data provider.

A data provider defines a common interface for Nutrient to load PDF documents from arbitrary sources like cloud hosts, device RAM, content providers, and others.

## Existing data provider classes

Nutrient comes with a range of predefined data providers that all implement [`DataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-data-provider/index.html):

- [`AssetDataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-asset-data-provider/index.html) allows loading of documents directly from the app’s `assets/` directory. This is useful if you ship PDF documents as part of your APK file. Note that copying assets to the internal device storage may perform better than reading them directly from the assets using this provider.

- [`ContentResolverDataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-content-resolver-data-provider/index.html) uses Android’s content resolver framework for reading documents directly from a [`ContentProvider`](http://developer.android.com/reference/android/content/ContentProvider.html) specified by a URI using the `content://` scheme.

- [`InputStreamDataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-input-stream-data-provider/index.html) is an abstract base class that simplifies reading documents from an [`InputStream`](http://developer.android.com/reference/java/io/InputStream.html). Subclasses have to override the `openInputStream()` method to provide the ready-to-read stream. Be aware that while it’s convenient to use an [`InputStream`](http://developer.android.com/reference/java/io/InputStream.html), it can pose performance issues. This is caused by the fact that PDF documents are read using random access, whereas [`InputStream`](http://developer.android.com/reference/java/io/InputStream.html) only offers stream access. Therefore, [`InputStreamDataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-input-stream-data-provider/index.html) will reopen the underlying input stream every time it needs to “seek backward.”

- `AesDataProvider` is shipped with the Catalog app and allows you to open AES256-CTR-encrypted files without storing the decrypted blocks anywhere. It supports random seeking and can handle large PDF files without causing `OutOfMemoryExceptions`.

## Custom data provider

To create a custom data provider for your application, you’ll have to create a class that implements the [`DataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-data-provider/index.html) interface and all of its methods. If you’d like to use your data provider with [`PdfActivity`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui/-pdf-activity/index.html), your class also needs to implement Android’s [`Parcelable`](http://developer.android.com/reference/android/os/Parcelable.html) interface. If you plan to use the data provider directly with the [`PdfFragment`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui/-pdf-fragment/index.html), you don’t need to make it into a Parcelable.

Take a look at `CustomDataProviderExample` inside the Catalog app. This shows how to create a data provider that can read a PDF document from the app’s `res/raw/` directory using an [`InputStream`](http://developer.android.com/reference/java/io/InputStream.html).











### Writeable data providers

If you want your custom [`DataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-data-provider/index.html) to also be writeable, you need to implement the [`WritableDataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-writable-data-provider/index.html) interface. This tells the framework that your [`DataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-data-provider/index.html) also supports writing changes made to the data. Here’s the outline of how this would appear:

### KOTLIN

```kotlin

class ExampleDataProvider : InputStreamDataProvider(), WritableDataProvider {...

    // Tells the system we can write to this data provider.
    override fun canWrite(): Boolean = true

    override fun startWrite(writeMode: WritableDataProvider.WriteMode): Boolean {
        when (writeMode) {
            WritableDataProvider.WriteMode.REWRITE_FILE -> {
                // Prepare for writing, e.g. creating a new
                // temporary file to write to....

                // Return `true` to indicate we can proceed with writing.
                return true
            }
            WritableDataProvider.WriteMode.APPEND_TO_FILE -> {
                // This won't occur when returning `false` in
                // `supportsAppending`.
                return false
            }
        }
    }

    // This gets called repeatedly with the data we need to write.
    // Depending on the current write mode, either append
    // it to the existing data or write to a new file.
    override fun write(data: ByteArray): Boolean {...

        // Return `true` to indicate we can proceed with writing.
        return true
    }

    // This is called once all data is written to give you an
    // opportunity to finish your writing process.
    override fun finishWrite(): Boolean {...

        // Return `true` to indicate writing was successful.
        return true
    }

    // If you support appending data, you can return `true`. For this simple
    // example, we just return `false`.
    // Returning `true` doesn't mean it will always append;
    // you still need to support both write modes.
    override fun supportsAppending(): Boolean = false
}

```

### JAVA

```java

class ExampleDataProvider extends InputStreamDataProvider implements WritableDataProvider {...

    @Override
    public boolean canWrite() {
        // Tells the system we can write to this data provider.
        return true;
    }

    @Override
    public boolean startWrite(WriteMode writeMode) {
        switch (writeMode) {
            case REWRITE_FILE:
                // Prepare for writing, e.g. creating a new
                // temporary file to write to....

                // Return `true` to indicate we can proceed with writing.
                return true;
            case APPEND_TO_FILE:
                // This won't occur when returning `false` in
                // `supportsAppending`.
                return false;
        }

        return false;
    }

    // This gets called repeatedly with the data we need to write.
    // Depending on the current write mode, either append
    // it to the existing data or write to a new file.
    @Override
    public boolean write(byte[] data) {...

        // Return `true` to indicate we can proceed with writing.
        return true;
    }

    // This is called once all data is written to give you an
    // opportunity to finish your writing process.
    @Override
    public boolean finishWrite() {...

        // Return `true` to indicate writing was successful.
        return true;
    }

    // If you support appending data you can return `true`. For this
    // simple example, we just return `false`.
    // Returning `true` doesn't mean it will always append;
    // you still need to support both write modes.
    @Override
    public boolean supportsAppending() {
        return false;
    }
}

```

For a complete example, check out `AesDataProvider`, which is part of our Catalog app.




## More uses for data providers

In certain cases, it can be beneficial to use a [`DataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-data-provider/index.html) not just for displaying documents, but also to write data. Certain APIs, such as [`XfdfFormatter`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.formatters/-xfdf-formatter/index.html) and [`DocumentJsonFormatter`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.formatters/-document-json-formatter/index.html), already allow you to use a [`DataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-data-provider/index.html) for the input. When using the [`OutputStreamAdapter`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-output-stream-adapter/index.html), you can also use the same [`DataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-data-provider/index.html) for the output, so long as it implements [`WritableDataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-writable-data-provider/index.html). Let’s look at an example of how to store your XFDF data encrypted:

### KOTLIN

```kotlin

val file =...
val annotations =...
val formFields =...
// `AesDataProvider` is a sample data provider found in our Catalog app.
val dataProvider = AesDataProvider(file.canonicalPath, BASE64_ENCRYPTION_KEY)

// Export all annotations in the document to our data provider.
XfdfFormatter.writeXfdf(document,
    annotations,
    formFields,
    OutputStreamAdapter.Builder.fromDataProvider(dataProvider).build())

// You can use the same data provider for reimporting.
val parsedAnnotations = XfdfFormatter.parseXfdf(document, dataProvider)

```

### JAVA

```java

File file =...
List<Annotation> annotations =...
List<FormField> formFields =...
// `AesDataProvider` is a sample data provider found in our Catalog app.
AesDataProvider dataProvider = new AesDataProvider(file.canonicalPath,
    BASE64_ENCRYPTION_KEY);

// Export all annotations in the document to our data provider.
XfdfFormatter.writeXfdf(getDocument(),
    annotations,
    formFields,
    OutputStreamAdapter.Builder.fromDataProvider(dataProvider).build());

// You can use the same data provider for reimporting.
List<Annotation> parsedAnnotations = XfdfFormatter.parseXfdf(getDocument(),
    dataProvider);

```

### Writing strategies

The [`OutputStreamAdapter`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-output-stream-adapter/index.html) can use a different [`WritingStrategy`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-writing-strategy/index.html) depending on the requirements of the given [`DataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-data-provider/index.html). By default, we provide two [`WritingStrategy`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-writing-strategy/index.html) implementations:

1. [`DirectWritingStrategy`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-direct-writing-strategy/index.html) — This writes immediately to the [`DataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-data-provider/index.html) and is used by default.

2. [`TempFileWritingStrategy`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-temp-file-writing-strategy/index.html) — This writes to a temporary file and only commits it to the [`DataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-data-provider/index.html) once all data is ready. This is useful if the operation writing to your [`DataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-data-provider/index.html) is simultaneously reading from it.
---

## Related pages

- [Incremental PDF saving on Android](/guides/android/faq/growing-pdf-file-size.md)
- [Auto saving PDF files on Android](/guides/android/features/document-checkpointing.md)
- [Saving a PDF file on Android](/guides/android/save-a-document.md)
- [How to save documents as PDFs on Android](/guides/android/save-a-document/save-as.md)
- [Save PDFs to local storage on Android](/guides/android/save-a-document/to-local-storage.md)
- [Save PDFs to Document Engine on Android](/guides/android/save-a-document/to-document-engine.md)
- [Save PDFs to a remote server on Android](/guides/android/save-a-document/to-remote-server.md)

