---
title: "Search Indexing"
canonical_url: "https://www.nutrient.io/guides/android/samples/search-indexing-kotlin/"
md_url: "https://www.nutrient.io/guides/android/samples/search-indexing-kotlin.md"
last_updated: "2026-05-15T19:10:04.916Z"
description: "Index PDF assets and search them with PdfLibrary and LibraryFileSystemDataSource."
---

# Search Indexing

Index PDF assets and search them with PdfLibrary and LibraryFileSystemDataSource.

[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 © 2025-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.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Checkbox
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
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.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import com.pspdfkit.catalog.R
import com.pspdfkit.catalog.SdkExample
import com.pspdfkit.catalog.ui.theming.CatalogTheme
import com.pspdfkit.configuration.activity.PdfActivityConfiguration
import com.pspdfkit.document.library.LibraryFileSystemDataSource
import com.pspdfkit.document.library.PdfLibrary
import com.pspdfkit.document.library.QueryOptions
import com.pspdfkit.document.library.QueryPreviewResult
import com.pspdfkit.document.library.QueryResultListener
import com.pspdfkit.ui.PdfActivityIntentBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File

class IndexedFullTextSearchExample(context: Context) :
    SdkExample(
        context,
        R.string.indexedFtsExampleTitle,
        R.string.indexedFtsExampleDescription,
    ) {
    override fun launchExample(context: Context, configuration: PdfActivityConfiguration.Builder) {
        val intent = Intent(context, IndexedFullTextSearchActivity::class.java)
        context.startActivity(intent)
    }
}

class IndexedFullTextSearchActivity : ComponentActivity() {
    companion object {
        private const val EXTRACTED_PDFS_DIR = "extracted_pdfs"
    }

    private var pdfLibrary: PdfLibrary? = null
    private var dataSource: LibraryFileSystemDataSource? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            CatalogTheme {
                LibrarySearchScreen()
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        dataSource?.cleanup()
        pdfLibrary = null
        dataSource = null
    }

    @Composable
    private fun LibrarySearchScreen() {
        var searchQuery by remember { mutableStateOf("") }
        var searchResults by remember { mutableStateOf<Map<String, Set<QueryPreviewResult>>>(emptyMap()) }
        var isIndexing by remember { mutableStateOf(true) }
        var indexingProgress by remember { mutableStateOf("Preparing to index...") }
        var matchExactPhrases by remember { mutableStateOf(false) }
        var extractedDirectory by remember { mutableStateOf<File?>(null) }

        LaunchedEffect(Unit) {
            withContext(Dispatchers.IO) {
                try {
                    extractedDirectory = extractAllPdfAssets()
                    initializePdfLibrary(extractedDirectory!!) { progress ->
                        indexingProgress = progress
                        if (progress.contains("completed")) {
                            isIndexing = false
                        }
                    }
                } catch (e: Exception) {
                    indexingProgress = "Error: ${e.message}"
                    isIndexing = false
                }
            }
        }

        LaunchedEffect(searchQuery, isIndexing, matchExactPhrases) {
            if (searchQuery.isNotEmpty() && pdfLibrary!= null &&!isIndexing) {
                performSearch(searchQuery, matchExactPhrases) { results ->
                    searchResults = results
                }
            } else if (searchQuery.isEmpty()) {
                searchResults = emptyMap()
            }
        }

        DisposableEffect(Unit) {
            onDispose {
                dataSource?.cleanup()
            }
        }

        Scaffold(
            modifier = Modifier.fillMaxSize(),
        ) { paddingValues ->
            Column(
                modifier =
                Modifier.fillMaxSize().padding(paddingValues).padding(16.dp),
            ) {
                OutlinedTextField(
                    value = searchQuery,
                    onValueChange = { searchQuery = it },
                    label = { Text("Search PDFs") },
                    placeholder = { Text("Enter search terms...") },
                    modifier = Modifier.fillMaxWidth(),
                    enabled =!isIndexing,
                )

                Row(
                    verticalAlignment = Alignment.CenterVertically,
                    modifier = Modifier.padding(vertical = 4.dp),
                ) {
                    Checkbox(
                        checked = matchExactPhrases,
                        onCheckedChange = { matchExactPhrases = it },
                        enabled =!isIndexing,
                    )
                    Text(
                        text = "Match exact phrases",
                        style = MaterialTheme.typography.bodyMedium,
                        modifier = Modifier.clickable { matchExactPhrases =!matchExactPhrases },
                    )
                }

                if (isIndexing) {
                    Column(
                        modifier =
                        Modifier.fillMaxWidth().padding(vertical = 16.dp),
                        horizontalAlignment = Alignment.CenterHorizontally,
                    ) {
                        CircularProgressIndicator()
                        Text(
                            text = indexingProgress,
                            style = MaterialTheme.typography.bodyMedium,
                            modifier = Modifier.padding(top = 8.dp),
                        )
                    }
                } else if (searchQuery.isEmpty()) {
                    Text(
                        text = "Enter a search term to find content in the PDF library",
                        style = MaterialTheme.typography.bodyLarge,
                        modifier = Modifier.padding(vertical = 16.dp),
                    )
                } else if (searchResults.isEmpty()) {
                    Text(
                        text = "No results found for \"$searchQuery\"",
                        style = MaterialTheme.typography.bodyLarge,
                        modifier = Modifier.padding(vertical = 16.dp),
                    )
                }

                LazyColumn(
                    verticalArrangement = Arrangement.spacedBy(8.dp),
                ) {
                    searchResults.forEach { (_, results) ->
                        items(results.toList()) { result ->
                            SearchResultCard(
                                result = result,
                                onResultClick = { clickedResult ->
                                    openDocumentAtPage(clickedResult)
                                },
                            )
                        }
                    }
                }
            }
        }
    }

    @Composable
    private fun SearchResultCard(result: QueryPreviewResult, onResultClick: (QueryPreviewResult) -> Unit) {
        Card(
            modifier =
            Modifier.fillMaxWidth().clickable { onResultClick(result) },
            elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
        ) {
            Column(
                modifier = Modifier.padding(12.dp),
            ) {
                Text(
                    text = "Document: ${getDocumentName(result.uid)}",
                    style = MaterialTheme.typography.titleMedium,
                    fontWeight = FontWeight.Bold,
                )
                Text(
                    text = "Page ${result.pageIndex + 1}",
                    style = MaterialTheme.typography.bodySmall,
                    color = MaterialTheme.colorScheme.onSurfaceVariant,
                    modifier = Modifier.padding(bottom = 4.dp),
                )
                val previewText = result.previewText
                val range = result.rangeInPreviewText
                val highlightStart = range.startPosition.coerceIn(0, previewText.length)
                val highlightEnd = (range.startPosition + range.length).coerceIn(highlightStart, previewText.length)
                Text(
                    text = buildAnnotatedString {
                        append(previewText.substring(0, highlightStart))
                        withStyle(SpanStyle(fontWeight = FontWeight.Bold, background = MaterialTheme.colorScheme.primaryContainer)) {
                            append(previewText.substring(highlightStart, highlightEnd))
                        }
                        append(previewText.substring(highlightEnd))
                    },
                    style = MaterialTheme.typography.bodyMedium,
                    modifier = Modifier.padding(top = 4.dp),
                )
            }
        }
    }

    private suspend fun extractAllPdfAssets(): File = withContext(Dispatchers.IO) {
        val extractedDir = File(filesDir, EXTRACTED_PDFS_DIR)
        if (!extractedDir.exists()) {
            extractedDir.mkdirs()
        }

        val assetManager = assets
        val pdfAssets = assetManager.list("")?.filter { it.endsWith(".pdf") }?: emptyList()

        for (assetName in pdfAssets) {
            val outputFile = File(extractedDir, assetName)
            if (!outputFile.exists()) {
                assetManager.open(assetName).use { input ->
                    outputFile.outputStream().use { output ->
                        input.copyTo(output)
                    }
                }
            }
        }

        extractedDir
    }

    private suspend fun initializePdfLibrary(documentsDirectory: File, onProgress: (String) -> Unit) {
        withContext(Dispatchers.IO) {
            try {
                onProgress("Initializing PDF library...")

                val libraryDbPath = File(filesDir, "pdf_library.db").absolutePath
                pdfLibrary = PdfLibrary(libraryDbPath)
                val library = pdfLibrary?: throw IllegalStateException("Failed to create PdfLibrary instance")
                onProgress("Setting up data source...")
                dataSource = LibraryFileSystemDataSource(library, documentsDirectory)
                library.dataSource = dataSource

                onProgress("Indexing...")
                library.updateIndexFromDataSource()

                onProgress("Indexing completed!")
            } catch (e: Exception) {
                onProgress("Error during initialization: ${e.message}")
                throw e
            }
        }
    }

    private fun performSearch(query: String, matchExactPhrases: Boolean, onResults: (Map<String, Set<QueryPreviewResult>>) -> Unit) {
        val library = pdfLibrary?: return

        val queryOptions = QueryOptions.Builder().generateTextPreviews(true).matchExactPhrases(matchExactPhrases).maximumPreviewResultsPerDocument(3).maximumPreviewResultsTotal(20).build()

        library.search(
            query,
            queryOptions,
            object : QueryResultListener {
                override fun onSearchCompleted(searchString: String, results: Map<String, Set<Int>>) {
                    // Basic results handled by preview callback
                }

                override fun onSearchPreviewsGenerated(searchString: String, results: Map<String, Set<QueryPreviewResult>>) {
                    runOnUiThread {
                        onResults(results)
                    }
                }
            },
        )
    }

    private fun openDocumentAtPage(result: QueryPreviewResult) {
        val descriptor = dataSource?.indexItemDescriptorForDocumentWithUid(result.uid)
        if (descriptor!= null) {
            try {
                // Construct the full path to the extracted file
                val extractedDir = File(filesDir, EXTRACTED_PDFS_DIR)
                val documentFile = File(extractedDir, descriptor.documentPath)
                val fileUri = Uri.fromFile(documentFile)

                val configuration =
                    PdfActivityConfiguration.Builder(this).page(result.pageIndex).build()

                val intent =
                    PdfActivityIntentBuilder.fromUri(this, fileUri).configuration(configuration).build()

                startActivity(intent)
            } catch (e: Exception) {
                // Handle error - could show a toast or log
                android.util.Log.e("LibraryAssetSearch", "Error opening document: ${e.message}")
            }
        }
    }

    private fun getDocumentName(uid: String): String {
        val descriptor = dataSource?.indexItemDescriptorForDocumentWithUid(uid)
        return descriptor?.documentPath?.substringAfterLast("/")?: "Unknown Document"
    }
}

```

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

---

## Related pages

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

