---
title: "Replace and Refresh Documents"
canonical_url: "https://www.nutrient.io/guides/android/samples/replace-and-refresh-documents-kotlin/"
md_url: "https://www.nutrient.io/guides/android/samples/replace-and-refresh-documents-kotlin.md"
last_updated: "2026-11-02T14:08:15.330Z"
description: "Swap the displayed document or reload the current one on a hoisted DocumentState with setCustomPdfSource — without recreating the DocumentView."
---

# Replace and Refresh Documents

Swap the displayed document or reload the current one on a hoisted DocumentState with setCustomPdfSource — without recreating the DocumentView.

[Get Started](https://www.nutrient.io/sdk/android/getting-started.md)

[All Samples](https://www.nutrient.io/guides/android/samples.md)

[Download](https://www.nutrient.io/guides/android/downloads.md)

[Launch Demo](https://www.nutrient.io/demo/)

---

```kotlin

/*
 *   Copyright © 2026 PSPDFKit GmbH. All rights reserved.
 *
 *   The PSPDFKit Sample applications are licensed with a modified BSD license.
 *   Please see License for details. This notice may not be removed from this file.
 */

package com.pspdfkit.catalog.examples.kotlin

import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.pspdfkit.catalog.R
import com.pspdfkit.catalog.SdkExample
import com.pspdfkit.catalog.tasks.ExtractAssetTask
import com.pspdfkit.catalog.ui.theming.CatalogTheme
import com.pspdfkit.configuration.activity.PdfActivityConfiguration
import com.pspdfkit.document.DocumentSource
import com.pspdfkit.jetpack.compose.interactors.DefaultListeners
import com.pspdfkit.jetpack.compose.interactors.DocumentState
import com.pspdfkit.jetpack.compose.interactors.getDefaultDocumentManager
import com.pspdfkit.jetpack.compose.interactors.rememberDocumentState
import com.pspdfkit.jetpack.compose.views.DocumentView
import com.pspdfkit.utils.getSupportParcelableExtra

/**
 * Replacing and refreshing the displayed document **without recreating the composable**, through
 * [DocumentState.setCustomPdfSource] on a hoisted [DocumentState]. The contract:
 *
 * - **When it is safe to call:** any time after the [DocumentView] has composed and attached.
 *   Calling it before that is a silent no-op (no exception, no log).
 * - **The call is asynchronous.** Completion is observed through the `DocumentManager`'s
 *   `onDocumentLoaded` callback — it fires again for the new document once loading completes,
 *   the same way it does for the initial document.
 * - **Preserved:** the underlying fragment, the [PdfActivityConfiguration], and every listener
 *   registered through the `DocumentManager`.
 * - **Reset:** the zoom resets to 1.0 on every reload. The current page index carries over into
 *   the new document as-is; with `restoreLastViewedPage` enabled on the configuration (the
 *   default), the incoming document's own last-viewed page can then take over once loading
 *   completes.
 * - **Guard the page index before replacing:** because the index carries over unguarded,
 *   swapping to a document with fewer pages than the current index can crash. Set a valid page
 *   first — `documentState.documentConnection.setPageIndex(0)` — before calling
 *   [DocumentState.setCustomPdfSource] with a different, potentially shorter document.
 * - **Replace vs. refresh:** pass a [DocumentSource] to swap the displayed document; pass
 *   `null` to reload the current source (e.g. after its backing file changed on disk).
 * - **Keeping the reader's place:** capture [DocumentState.getViewState] before the call and
 *   apply [DocumentState.setViewState] from `onDocumentLoaded`. This example keeps one bundle
 *   per document, so switching back restores that document's page, zoom, and viewport — a
 *   bundle is only ever restored onto the same document it was captured from.
 */
class CustomPdfSourceExample(context: Context) :
    SdkExample(context, R.string.customPdfSourceExampleTitle, R.string.customPdfSourceExampleDescription) {
    override fun launchExample(context: Context, configuration: PdfActivityConfiguration.Builder) {
        // Extract both documents up front so the swap button has a target ready.
        ExtractAssetTask.extract(WELCOME_DOC, title, context) { firstDocument ->
            ExtractAssetTask.extract(ANNOTATIONS_EXAMPLE, title, context) { secondDocument ->
                val intent = Intent(context, CustomPdfSourceActivity::class.java).putExtra(CustomPdfSourceActivity.EXTRA_FIRST, Uri.fromFile(firstDocument)).putExtra(CustomPdfSourceActivity.EXTRA_SECOND, Uri.fromFile(secondDocument))
                    // The catalog-prepared configuration carries the catalog theme and the
                    // user's preference toggles — pass it through instead of rebuilding..putExtra(CustomPdfSourceActivity.EXTRA_CONFIG, configuration.build())
                context.startActivity(intent)
            }
        }
    }
}

class CustomPdfSourceActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        enableEdgeToEdge()
        super.onCreate(savedInstanceState)
        val first = intent.getSupportParcelableExtra(EXTRA_FIRST, Uri::class.java)!!
        val second = intent.getSupportParcelableExtra(EXTRA_SECOND, Uri::class.java)!!
        val configuration = intent.getSupportParcelableExtra(EXTRA_CONFIG, PdfActivityConfiguration::class.java)

        setContent {
            CatalogTheme {
                CustomPdfSourceDocument(first, second, configuration)
            }
        }
    }

    companion object {
        const val EXTRA_FIRST = "CustomPdfSourceActivity.FirstDocument"
        const val EXTRA_SECOND = "CustomPdfSourceActivity.SecondDocument"
        const val EXTRA_CONFIG = "CustomPdfSourceActivity.Configuration"
    }
}

@Composable
private fun CustomPdfSourceDocument(first: Uri, second: Uri, baseConfiguration: PdfActivityConfiguration?) {
    val context = androidx.compose.ui.platform.LocalContext.current
    val configuration = remember(baseConfiguration) {
        // Layer the example's needs on top of the catalog-prepared configuration (theme,
        // preference toggles) instead of discarding it.
        (baseConfiguration?.let { PdfActivityConfiguration.Builder(it) }?: PdfActivityConfiguration.Builder(context))
            // The buttons below occupy the strip where the document title overlay would render..documentTitleOverlayEnabled(false).build()
    }
    // The state is hoisted once with the FIRST document; swapping never recreates it — the
    // fragment, configuration, and DocumentManager listeners all survive the source change.
    // File-backed documents open through their Uri (native, memory-mapped); a stream-backed
    // DataProvider would buffer the whole document onto the Java heap.
    val documentState = rememberDocumentState(first, configuration)

    // Which source is currently requested, and whether a swap/refresh is still loading.
    var showingSecond by remember { mutableStateOf(false) }
    var isLoading by remember { mutableStateOf(false) }
    var loadedDocumentTitle by remember { mutableStateOf<String?>(null) }

    // Continuity across the swap: each document's view state (page, zoom, viewport) is captured
    // when leaving it and re-applied from onDocumentLoaded when returning to it — a bundle is
    // only ever restored onto the SAME document it was captured from. A refresh restores the
    // state it captured right before the call, so the reader keeps their place.
    val savedViewStates = remember { mutableMapOf<Boolean, Bundle?>() }
    var pendingViewStateRestore by remember { mutableStateOf<Bundle?>(null) }

    // The buttons follow the SDK chrome: hidden while the UI is hidden (immersive mode) and
    // while any special mode is active (annotation / document / content editing — signaled by a
    // non-null contextual toolbar), so they never sit under the top-docked contextual bar or
    // distract from an active tool.
    var uiVisible by remember { mutableStateOf(true) }
    val coordinator by documentState.toolbarCoordinator.collectAsState()
    val modeActive = coordinator?.contextualToolbar?.collectAsState()?.value!= null

    Box(Modifier.fillMaxSize()) {
        DocumentView(
            documentState = documentState,
            modifier = Modifier.fillMaxSize(),
            documentManager = getDefaultDocumentManager(
                documentListener = DefaultListeners.documentListeners(
                    // The completion signal for setCustomPdfSource: fires again for the new
                    // document once the asynchronous replacement/refresh finishes.
                    onDocumentLoaded = { document ->
                        isLoading = false
                        loadedDocumentTitle = document.title
                        // Restore the page, zoom, and viewport captured for THIS document —
                        // applied after load so it also wins over restoreLastViewedPage.
                        pendingViewStateRestore?.let { documentState.setViewState(it) }
                        pendingViewStateRestore = null
                    },
                ),
                uiListener = DefaultListeners.uiListeners(
                    onImmersiveModeEnabled = { immersive -> uiVisible =!immersive },
                ),
            ),
        )

        // Anchored below the SDK main toolbar (status-bar strip + the standard 56dp toolbar
        // row) — the bottom edge belongs to the thumbnail bar. Fixed clearance on purpose:
        // measuring through the fragment would read 0 before it attaches.
        AnimatedVisibility(
            visible = uiVisible &&!modeActive,
            enter = slideInVertically { -it } + fadeIn(),
            exit = slideOutVertically { -it } + fadeOut(),
            modifier = Modifier.align(Alignment.TopCenter).windowInsetsPadding(WindowInsets.statusBars).padding(top = 56.dp + 12.dp),
        ) {
            SourceButtons(
                showingSecond = showingSecond,
                isLoading = isLoading,
                onSwap = {
                    // Capture the leaving document's place FIRST (before the page-index guard
                    // below clobbers it), so switching back restores page, zoom, and viewport.
                    savedViewStates[showingSecond] = documentState.getViewState()
                    // Guard the page index BEFORE replacing: it carries over into the new
                    // document unguarded, and a shorter document can crash on an out-of-range
                    // index. Page 0 is always valid.
                    documentState.documentConnection.setPageIndex(0)
                    isLoading = true
                    showingSecond =!showingSecond
                    // The incoming document's own saved place, if it was visited before.
                    pendingViewStateRestore = savedViewStates[showingSecond]
                    val target = if (showingSecond) second else first
                    // Replace: asynchronous — completion arrives in onDocumentLoaded above.
                    documentState.setCustomPdfSource(DocumentSource(target))
                },
                onRefresh = {
                    // Same-document reload: capture now, restore from onDocumentLoaded — the
                    // reload alone would reset the zoom to 1.0.
                    pendingViewStateRestore = documentState.getViewState()
                    isLoading = true
                    // Refresh: null reloads the CURRENT source (same completion signal).
                    documentState.setCustomPdfSource(null)
                },
            )
        }

        if (isLoading) {
            CircularProgressIndicator(Modifier.align(Alignment.Center))
        }
    }
    // Back unwinds like PdfActivity: while a mode or an overlay view is active, let the SDK
    // close it the layered way (contextual toolbar first, then the overlay); only the default
    // viewer state lets the system back finish the example. Both gate signals are observable
    // flows, so the handler's enabled state never goes stale.
    val overlayShown by documentState.viewWithOverlappingToolbarShown.collectAsState()
    BackHandler(enabled = modeActive || overlayShown) {
        documentState.handleBackPress()
    }
}

@Composable
private fun SourceButtons(showingSecond: Boolean, isLoading: Boolean, onSwap: () -> Unit, onRefresh: () -> Unit) {
    Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
        Button(enabled =!isLoading, onClick = onSwap) {
            Text(if (showingSecond) "Show first document" else "Show second document")
        }
        OutlinedButton(enabled =!isLoading, onClick = onRefresh) {
            Text("Refresh")
        }
    }
}

```

This code sample is an example that illustrates how to use our SDK. Please adapt it to your specific use case.

---

## Related pages

- [Add LTV to Existing Signature](/guides/android/samples/add-ltv-to-existing-signature-kotlin.md)
- [AES Encrypted File](/guides/android/samples/aes-encrypted-file-java.md)
- [AI Assistant (Multiple Documents, Compose)](/guides/android/samples/ai-assistant-multiple-documents-compose-kotlin.md)
- [AI Assistant (Multiple Documents, ViewPager)](/guides/android/samples/ai-assistant-multiple-documents-viewpager-kotlin.md)
- [AI Assistant (Single Document)](/guides/android/samples/ai-assistant-single-document-kotlin.md)
- [Annotation Configuration](/guides/android/samples/annotation-configuration-kotlin.md)
- [Annotation Creation](/guides/android/samples/annotation-creation-kotlin.md)
- [Annotation Flags](/guides/android/samples/annotation-flags-kotlin.md)
- [Annotation Overlay](/guides/android/samples/annotation-overlay-java.md)
- [Annotation Rendering](/guides/android/samples/annotation-rendering-kotlin.md)
- [Annotation Selection Styling](/guides/android/samples/annotation-selection-styling-kotlin.md)
- [Annotation Sidebar](/guides/android/samples/annotation-sidebar-kotlin.md)
- [Annotation Toolbar: Custom Items](/guides/android/samples/annotation-toolbar-custom-items-kotlin.md)
- [Annotation Toolbar: Item Grouping](/guides/android/samples/annotation-toolbar-item-grouping-kotlin.md)
- [Annotation Toolbar: User-Editable Tools](/guides/android/samples/annotation-toolbar-user-editable-tools-kotlin.md)
- [Annotations with Transparency](/guides/android/samples/annotations-with-transparency-kotlin.md)
- [Application Policy](/guides/android/samples/application-policy-kotlin.md)
- [Bookmark Highlighting](/guides/android/samples/bookmark-highlighting-kotlin.md)
- [Compose Image Document](/guides/android/samples/compose-image-document-kotlin.md)
- [Compose Navigation](/guides/android/samples/compose-navigation-kotlin.md)
- [Compose PDF Viewer](/guides/android/samples/compose-pdf-viewer-kotlin.md)
- [Construction Floor Plan](/guides/android/samples/construction-floor-plan-kotlin.md)
- [Custom Activity Form Editing](/guides/android/samples/custom-activity-form-editing-java.md)
- [Custom Annotation Inspector](/guides/android/samples/custom-annotation-inspector-java.md)
- [Custom Data Provider](/guides/android/samples/custom-data-provider-kotlin.md)
- [Custom Download Dialog](/guides/android/samples/custom-download-dialog-java.md)
- [Custom Electronic Signature](/guides/android/samples/custom-electronic-signature-java.md)
- [Custom Form Highlight Color](/guides/android/samples/custom-form-highlight-color-java.md)
- [Custom Layout](/guides/android/samples/custom-layout-kotlin.md)
- [Custom Note Hinter](/guides/android/samples/custom-note-hinter-kotlin.md)
- [Custom Outline Provider](/guides/android/samples/custom-outline-provider-kotlin.md)
- [Custom Page Templates](/guides/android/samples/custom-page-templates-java.md)
- [Custom Search UI (Compose)](/guides/android/samples/custom-search-ui-compose-kotlin.md)
- [Custom Search UI (Views)](/guides/android/samples/custom-search-ui-views-java.md)
- [Custom Settings, Info and Outline UI](/guides/android/samples/custom-settings-info-and-outline-ui-kotlin.md)
- [Custom Sharing Dialog](/guides/android/samples/custom-sharing-dialog-java.md)
- [Custom Sharing Menu](/guides/android/samples/custom-sharing-menu-java.md)
- [Custom Stamp Annotations](/guides/android/samples/custom-stamp-annotations-java.md)
- [Digital Signature (Basic)](/guides/android/samples/digital-signature-basic-kotlin.md)
- [Digital Signature (Manual)](/guides/android/samples/digital-signature-manual-kotlin.md)
- [Digital Signature (Third-Party)](/guides/android/samples/digital-signature-third-party-kotlin.md)
- [Digital Signature (Two-Step)](/guides/android/samples/digital-signature-two-step-kotlin.md)
- [Disabled Annotation Property](/guides/android/samples/disabled-annotation-property-java.md)
- [Document Comparison](/guides/android/samples/document-comparison-kotlin.md)
- [Document Download](/guides/android/samples/document-download-kotlin.md)
- [Document from Canvas](/guides/android/samples/document-from-canvas-kotlin.md)
- [Document Processing](/guides/android/samples/document-processing-kotlin.md)
- [Document Sharing](/guides/android/samples/document-sharing-java.md)
- [Document Switcher](/guides/android/samples/document-switcher-java.md)
- [DocumentView Composable](/guides/android/samples/documentview-composable-kotlin.md)
- [DocumentView in a Dialog](/guides/android/samples/documentview-in-a-dialog-kotlin.md)
- [Download Progress](/guides/android/samples/download-progress-kotlin.md)
- [Dynamic Pages on Scroll](/guides/android/samples/dynamic-pages-on-scroll-kotlin.md)
- [E-Learning](/guides/android/samples/e-learning-kotlin.md)
- [Electronic + Digital Signing](/guides/android/samples/electronic-digital-signing-kotlin.md)
- [External Document](/guides/android/samples/external-document-kotlin.md)
- [File Annotation Creation](/guides/android/samples/file-annotation-creation-kotlin.md)
- [Filterable Thumbnail Grid](/guides/android/samples/filterable-thumbnail-grid-kotlin.md)
- [Form Click Intercept (Compose)](/guides/android/samples/form-click-intercept-compose-kotlin.md)
- [Form Creation](/guides/android/samples/form-creation-kotlin.md)
- [Form Filling](/guides/android/samples/form-filling-kotlin.md)
- [Forms with JavaScript](/guides/android/samples/forms-with-javascript-kotlin.md)
- [Fragment Runtime Configuration](/guides/android/samples/fragment-runtime-configuration-kotlin.md)
- [Generate PDF Report](/guides/android/samples/generate-pdf-report-kotlin.md)
- [Hide and Reveal Areas](/guides/android/samples/hide-and-reveal-areas-kotlin.md)
- [HTML-to-PDF Conversion](/guides/android/samples/html-to-pdf-conversion-kotlin.md)
- [Image Document](/guides/android/samples/image-document-kotlin.md)
- [Inline Multimedia](/guides/android/samples/inline-multimedia-kotlin.md)
- [Instant Document JSON](/guides/android/samples/instant-document-json-kotlin.md)
- [Instant JSON Attachment](/guides/android/samples/instant-json-attachment-kotlin.md)
- [JavaScript Actions](/guides/android/samples/javascript-actions-kotlin.md)
- [JavaScript Calculator](/guides/android/samples/javascript-calculator-kotlin.md)
- [JavaScript Form Filling](/guides/android/samples/javascript-form-filling-kotlin.md)
- [Kiosk Grid](/guides/android/samples/kiosk-grid-kotlin.md)
- [LTV Signature](/guides/android/samples/ltv-signature-kotlin.md)
- [Main Toolbar: Custom Layout & Items](/guides/android/samples/main-toolbar-custom-layout-items-kotlin.md)
- [Main Toolbar: FAB Menu (M3 Expressive)](/guides/android/samples/main-toolbar-fab-menu-m3-expressive-kotlin.md)
- [Main Toolbar: Floating Pill (M3 Expressive)](/guides/android/samples/main-toolbar-floating-pill-m3-expressive-kotlin.md)
- [Measurement Tools](/guides/android/samples/measurement-tools-kotlin.md)
- [Memory Pressure Recovery](/guides/android/samples/memory-pressure-recovery-kotlin.md)
- [Merge Documents](/guides/android/samples/merge-documents-kotlin.md)
- [Multimedia Annotations](/guides/android/samples/multimedia-annotations-kotlin.md)
- [Multiple Documents (Compose Pager)](/guides/android/samples/multiple-documents-compose-pager-kotlin.md)
- [OCR](/guides/android/samples/ocr-kotlin.md)
- [Overlay Views](/guides/android/samples/overlay-views-kotlin.md)
- [Overlay Visibility](/guides/android/samples/overlay-visibility-kotlin.md)
- [Password Protected PDF](/guides/android/samples/password-protected-pdf-kotlin.md)
- [PDF from Image](/guides/android/samples/pdf-from-image-kotlin.md)
- [PdfFragment](/guides/android/samples/pdffragment-kotlin.md)
- [PdfUiFragment](/guides/android/samples/pdfuifragment-kotlin.md)
- [Playground](/guides/android/samples/playground-kotlin.md)
- [Popup Toolbar: Custom Items](/guides/android/samples/popup-toolbar-custom-items-kotlin.md)
- [Programmatic Zoom](/guides/android/samples/programmatic-zoom-kotlin.md)
- [Reader View](/guides/android/samples/reader-view-kotlin.md)
- [Remote URL](/guides/android/samples/remote-url-kotlin.md)
- [Runtime Configuration](/guides/android/samples/runtime-configuration-kotlin.md)
- [Scientific Paper](/guides/android/samples/scientific-paper-kotlin.md)
- [Screen Reader](/guides/android/samples/screen-reader-java.md)
- [Search Indexing](/guides/android/samples/search-indexing-kotlin.md)
- [Selection Customization](/guides/android/samples/selection-customization-java.md)
- [Signature Parcelize](/guides/android/samples/signature-parcelize-kotlin.md)
- [Signature Storage Database](/guides/android/samples/signature-storage-database-kotlin.md)
- [Sound Extraction](/guides/android/samples/sound-extraction-kotlin.md)
- [Split Screen (Compose)](/guides/android/samples/split-screen-compose-kotlin.md)
- [Split View](/guides/android/samples/split-view-java.md)
- [Tabbed Documents (Compose)](/guides/android/samples/tabbed-documents-compose-kotlin.md)
- [Tabbed Documents](/guides/android/samples/tabbed-documents-kotlin.md)
- [Tabbed Documents (Persistent)](/guides/android/samples/tabbed-documents-persistent-kotlin.md)
- [Text Field Suggestions](/guides/android/samples/text-field-suggestions-kotlin.md)
- [Thumbnail Bar Modes](/guides/android/samples/thumbnail-bar-modes-kotlin.md)
- [Try Instant](/guides/android/samples/try-instant-kotlin.md)
- [UI View Modes](/guides/android/samples/ui-view-modes-kotlin.md)
- [Vertical Scrollbar](/guides/android/samples/vertical-scrollbar-java.md)
- [Watermarks](/guides/android/samples/watermarks-kotlin.md)
- [XFDF Import/Export](/guides/android/samples/xfdf-import-export-kotlin.md)

