---
title: "Open PDF from custom data provider on Android | Nutrient"
canonical_url: "https://www.nutrient.io/guides/android/features/data-providers/"
md_url: "https://www.nutrient.io/guides/android/features/data-providers.md"
last_updated: "2026-05-25T16:07:03.287Z"
description: "In addition to loading documents using a URI, Nutrient can load documents using a DataProvider. A data provider defines a common interface."
---

# Open PDFs from a custom data provider on Android

In addition to loading documents using a URI, Nutrient can load documents using a [`DataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-data-provider/index.html). 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.

Launching a [`PdfActivity`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui/-pdf-activity/index.html) from a data provider works just like starting it using a `Uri`. Here is an example using the [`AssetDataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-asset-data-provider/index.html) for loading a PDF document from the `assets/` directory of your app:

### KOTLIN

```kotlin

// Create a data provider that reads directly from the app's assets.
val dataProvider = AssetDataProvider("document.pdf")

// Launch the activity using the data provider.
val intent = PdfActivityIntentBuilder.fromDataProvider(context, dataProvider).configuration(configuration.build()).build()
context.startActivity(intent)

```

### JAVA

```java

// Create a data provider that reads directly from the app's assets.
DataProvider dataProvider = new AssetDataProvider("document.pdf");

// Launch the activity using the data provider.
final Intent intent = PdfActivityIntentBuilder.fromDataProvider(context, dataProvider).configuration(configuration.build()).build();
context.startActivity(intent);

```

## 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).











### Data provider progress

If your custom [`DataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-data-provider/index.html) needs to prepare a document before it can be shown using Nutrient — for example, by downloading the PDF file from the web or by decrypting it up front — you can implement the [`ProgressDataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-progress-data-provider/index.html) interface and Nutrient will take care of showing a progress UI to your users. The interface defines a single method, [`#observeProgress`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-progress-data-provider/observe-progress.html), which has to return an RxJava [`Flowable`], which emits the current progress as a `Double` in the range of `[0, 1]`.

In case your [`ProgressDataProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-progress-data-provider/index.html) has no progress to report (e.g. if a file is already available), it can simply return [`ProgressDataProvider.COMPLETE`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-progress-data-provider/-companion/-c-o-m-p-l-e-t-e.html) inside [`#observeProgress`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.document.providers/-progress-data-provider/observe-progress.html).

Here’s a small example of a data provider that reports the progress of a file download. The full example can be found in our Catalog app as `ProgressProviderExample`:

### REMOTEDATAPROVIDER.KT

```kt

class RemoteDataProvider : InputStreamDataProvider, ProgressDataProvider {
    /** Internally, the data provider uses a subject to publish any download progress. */
    private val progressSubject = PublishSubject.create<Double>()
    /** Responsible for downloading our PDF.  */
    private var downloadJob: DownloadJob? = null
    /** Used to block document opening until the download is done. */
    private val downloadLatch = CountDownLatch(1)

    override fun observeProgress(): Flowable<Double> {
        // We can just return our `PublishSubject`.
        return progressSubject.toFlowable(BackpressureStrategy.LATEST)
    }

    override fun openInputStream(): InputStream {
        val job = startDownloadIfNotRunning()
        // Block document opening until the download is done.
        downloadLatch.await()
        // Once the download is complete, continue with the document opening.
        return FileInputStream(job.outputFile)
    }

    private fun startDownloadIfNotRunning(): DownloadJob {
        var job = downloadJob
        if (job!= null) return job

        job = DownloadJob.startDownload(...)
        job.setProgressListener(object : DownloadJob.ProgressListener {
            override fun onProgress(progress: Progress) {
                // Notify our listeners about the download progress.
                progressSubject.onNext(progress.bytesReceived.toDouble() / progress.totalBytes.toDouble())
            }

            override fun onComplete(output: File) {
                progressSubject.onComplete()
                // Unblock the actual document loading.
                downloadLatch.countDown()
            }...
        })

        downloadJob = job
        return job
    }
}

```

### REMOTEDATAPROVIDER.JAVA

```java

public static class RemoteDataProvider
    extends InputStreamDataProvider implements ProgressDataProvider {

    /** Internally, the data provider uses a subject to publish any download progress. */
    private PublishSubject<Double> progressSubject = PublishSubject.create();
    /** Responsible for downloading our PDF. */
    private DownloadJob downloadJob;
    /** Used to block document opening until the download is done. */
    private CountDownLatch downloadLatch = new CountDownLatch(1);

    @NonNull @Override protected InputStream openInputStream() throws Exception {
        startDownloadIfNotRunning();
        // Block document opening until the download is done.
        downloadLatch.await();
        // Once the download is complete, continue with the document opening.
        return new FileInputStream(downloadJob.getOutputFile());
    }

    @NonNull @Override public Flowable<Double> observeProgress() {
        // We can just return our `PublishSubject`.
        return progressSubject.toFlowable(BackpressureStrategy.LATEST);
    }

    private void startDownloadIfNotRunning() {
        if (downloadJob!= null) return;

        // In this short example, we use the PSPDFKit `DownloadJob` to download a PDF from the web.
        downloadJob = DownloadJob.startDownload(...)
        downloadJob.setProgressListener(new DownloadJob.ProgressListener() {
            @Override public void onProgress(@NonNull Progress progress) {
                // Notify our listeners about the download progress.
                progressSubject.onNext((double) progress.bytesReceived / (double) progress.totalBytes);
            }

            @Override public void onComplete(@NonNull File output) {
                progressSubject.onComplete();

                // Unblock the actual document loading.
                downloadLatch.countDown();
            }...
        });
    }
}

```

### 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.




## Avoiding in-memory data providers

In most cases, it is best to serve your PDF data from some kind of random access storage — usually a file. Keeping your PDF data solely in memory has two major disadvantages:

- It can quickly lead to `OutOfMemoryException` situations. This can happen when you don’t have full control over the size of your PDFs (e.g. when a user opens a big PDF) or when running your app on a device with less memory than anticipated.

- An in-memory data provider can’t be retained across [process recreations](https://developer.android.com/guide/components/activities/process-lifecycle.html). By design, Android can kill your app’s process as soon as all activities of your app are in the background. Once the app comes to the foreground, your process is recreated, leaving your in-memory data provider without data.

Due to these reasons, the previously available `MemoryDataProvider` was removed in [Nutrient Android SDK 3.1.1](https://www.nutrient.io/guides/android/changelog.md#3.1.1). Depending on your original usage scenario, you are advised to use one of the other available data provider classes.
---

## Related pages

- [Open a PDF file from Document Engine on Android](/guides/android/open-a-document/from-document-engine.md)
- [Open PDFs from in-memory data on Android](/guides/android/open-a-document/from-in-memory-data.md)
- [Open PDF files on Android](/guides/android/open-a-document.md)
- [Open a local PDF file on Android](/guides/android/open-a-document/from-local-storage.md)
- [Open password-protected PDFs on Android](/guides/android/security/secured-documents.md)
- [Open PDFs from URLs on Android](/guides/android/miscellaneous/document-downloads.md)

