This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /guides/android/save-a-document/to-custom-data-provider.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. Save PDF to custom data provider on Android | Nutrient SDK

Nutrient loads PDFs through objects that implement DataProvider. This interface supports sources such as cloud hosts, memory, and Android content providers.

Existing data provider classes

Nutrient provides these implementations of DataProvider:

  • AssetDataProvider loads PDFs from the app’s assets/ directory, such as documents bundled in an APK. Copying assets to internal storage can perform better than reading them directly.

  • ContentResolverDataProvider reads documents through Android’s content resolver. A content:// URI identifies the ContentProvider(opens in a new tab).

  • InputStreamDataProvider is an abstract base class for reading from an InputStream(opens in a new tab). Override openInputStream() to return a stream ready for reading.

    PDF reading requires random access, but an InputStream provides sequential access. InputStreamDataProvider reopens the stream when it needs an earlier position, which can reduce performance. For FileInputStream, it reads directly at the requested offset instead.

  • AesDataProvider, included in the Catalog app, opens AES256-CTR-encrypted files without storing decrypted blocks. It supports random access to large PDFs without loading the entire file into memory.

Custom data provider

To create a custom provider, implement DataProvider and all its methods. For use with PdfActivity, also implement Android’s Parcelable(opens in a new tab) interface. Direct use with PdfFragment doesn’t require Parcelable.

The Catalog app’s CustomDataProviderExample reads a PDF from res/raw/ through an InputStream(opens in a new tab).

Writeable data providers

If you want your custom DataProvider to also be writeable, you need to implement the WritableDataProvider interface. This tells the framework that your DataProvider also supports writing changes made to the data. Here’s the outline of how this would appear:

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
}

If canWrite() returns false, Nutrient treats the document as read-only when it’s opened. Editing UI that would require saving back to that provider isn’t offered.

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

More uses for data providers

XfdfFormatter and DocumentJsonFormatter accept a DataProvider as input. To use the provider for output, implement WritableDataProvider and wrap it in OutputStreamAdapter.

The following example stores encrypted XFDF data:

val document = activity.requirePdfFragment().document ?: return
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)

Writing strategies

Choose a WritingStrategy for OutputStreamAdapter based on your provider’s requirements: