Custom Search UI Compose
Showcases how to build a custom search view on top of the search API in Compose.
/* * 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.Contextimport android.content.Intentimport android.net.Uriimport android.os.Bundleimport androidx.activity.compose.setContentimport androidx.activity.viewModelsimport androidx.appcompat.app.AppCompatActivityimport androidx.compose.foundation.backgroundimport androidx.compose.foundation.layout.Boximport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.fillMaxWidthimport androidx.compose.foundation.layout.paddingimport androidx.compose.foundation.layout.statusBarsPaddingimport androidx.compose.material3.MaterialThemeimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Textimport androidx.compose.material3.TextFieldimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.setValueimport androidx.compose.ui.Modifierimport androidx.lifecycle.ViewModelimport androidx.lifecycle.ViewModelProviderimport androidx.lifecycle.viewModelScopeimport androidx.lifecycle.viewmodel.CreationExtrasimport com.pspdfkit.catalog.Rimport com.pspdfkit.catalog.SdkExampleimport com.pspdfkit.catalog.tasks.ExtractAssetTaskimport com.pspdfkit.catalog.ui.theming.CatalogThemeimport com.pspdfkit.configuration.PdfConfigurationimport com.pspdfkit.configuration.activity.PdfActivityConfigurationimport com.pspdfkit.configuration.activity.UserInterfaceViewModeimport com.pspdfkit.document.PdfDocumentimport com.pspdfkit.document.search.TextSearchimport com.pspdfkit.jetpack.compose.interactors.DefaultListenersimport com.pspdfkit.jetpack.compose.interactors.DocumentStateimport com.pspdfkit.jetpack.compose.interactors.getDefaultDocumentManagerimport com.pspdfkit.jetpack.compose.interactors.rememberDocumentStateimport com.pspdfkit.jetpack.compose.views.DocumentViewimport com.pspdfkit.ui.search.SearchResultHighlighterimport com.pspdfkit.utils.getSupportParcelableExtraimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.launchimport kotlin.getValue
class CustomSearchUiComposeExample(context: Context) : SdkExample(context, R.string.customSearchUiComposeExampleTitle, R.string.customSearchUiComposeExampleDescription) { /** Configuration is handled inside [CustomSearchUiComposeActivity] */ override fun launchExample(context: Context, configuration: PdfActivityConfiguration.Builder) { ExtractAssetTask.extract(WELCOME_DOC, title, context) { documentFile -> val intent = Intent(context, CustomSearchUiComposeActivity::class.java) intent.putExtra(CustomSearchUiComposeActivity.EXTRA_URI, Uri.fromFile(documentFile)) context.startActivity(intent) } }}
class CustomSearchUiComposeActivity : AppCompatActivity() {
private val viewModel: CustomSearchUiComposeViewModel by viewModels { CustomSearchUiComposeViewModel.Factory }
private var highlighter: SearchResultHighlighter? = null
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)
val uri = intent.getSupportParcelableExtra(EXTRA_URI, Uri::class.java)!!
highlighter = SearchResultHighlighter(this)
setContent { CatalogTheme { val searchQuery = viewModel.searchQuery
Scaffold( modifier = Modifier.background(color = MaterialTheme.colorScheme.onPrimary) .statusBarsPadding(), topBar = { TextField( value = searchQuery, onValueChange = viewModel::performSearch, modifier = Modifier.fillMaxWidth(), placeholder = { Text(text = "Search") } ) } ) { paddingValues ->
val pdfActivityConfiguration = PdfActivityConfiguration .Builder(this) .setUserInterfaceViewMode(UserInterfaceViewMode.USER_INTERFACE_VIEW_MODE_HIDDEN) .build()
val documentState = rememberDocumentState(uri, pdfActivityConfiguration)
Box(Modifier.padding(paddingValues)) { DocumentView( documentState = documentState, modifier = Modifier.fillMaxSize(), documentManager = getDefaultDocumentManager( documentListener = DefaultListeners.documentListeners( onDocumentLoaded = { document -> viewModel.onDocumentLoaded( document, pdfActivityConfiguration.configuration, documentState, highlighter ) } ) ) ) } } } } }
companion object { const val EXTRA_URI = "JetpackComposeActivity.DocumentUri" }}
class CustomSearchUiComposeViewModel : ViewModel() {
var searchQuery by mutableStateOf("") private set
private var document: PdfDocument? = null
private var textSearch: TextSearch? = null private var highlighter: SearchResultHighlighter? = null
fun onDocumentLoaded(document: PdfDocument, pdfConfiguration: PdfConfiguration, documentState: DocumentState, highlighter: SearchResultHighlighter?) { this.document = document textSearch = TextSearch( document, pdfConfiguration ) highlighter?.let { documentState.documentConnection.addDrawableProvider(it) } this.highlighter = highlighter }
fun performSearch(query: String) { searchQuery = query
viewModelScope.launch(Dispatchers.Default) { textSearch?.performSearch(searchQuery)?.let { results -> if (results.isNotEmpty()) { highlighter?.addSearchResults(results) } else { highlighter?.clearSearchResults() } } } }
companion object { val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create( modelClass: Class<T>, extras: CreationExtras ) = CustomSearchUiComposeViewModel() as T } }}This code sample is an example that illustrates how to use our SDK. Please adapt it to your specific use case.