---
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: "2025-07-02T00:00:00.000Z"
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

- [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)
- [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)
- [Construction Floor Plan](/guides/android/samples/construction-floor-plan-kotlin.md)
- [Custom ActionBar Actions](/guides/android/samples/custom-actionbar-actions-kotlin.md)
- [Custom Activity Form Editing](/guides/android/samples/custom-activity-form-editing-java.md)
- [Custom Activity Toolbars](/guides/android/samples/custom-activity-toolbars-java.md)
- [Custom Annotation Creation Toolbar](/guides/android/samples/custom-annotation-creation-toolbar-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 Main Toolbar](/guides/android/samples/custom-main-toolbar-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 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)
- [Custom Toolbar Grouping](/guides/android/samples/custom-toolbar-grouping-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)
- [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)
- [Inline Search](/guides/android/samples/inline-search-java.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)
- [Measurement Tools](/guides/android/samples/measurement-tools-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 Customization](/guides/android/samples/popup-toolbar-customization-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)
- [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 View](/guides/android/samples/split-view-java.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)

