---
title: "Kiosk Grid"
canonical_url: "https://www.nutrient.io/guides/android/samples/kiosk-grid-kotlin/"
md_url: "https://www.nutrient.io/guides/android/samples/kiosk-grid-kotlin.md"
last_updated: "2026-05-15T19:10:04.916Z"
description: "Browse documents from the assets folder in a grid with thumbnail previews."
---

# Kiosk Grid

Browse documents from the assets folder in a grid with thumbnail previews.

[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 © 2020-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.graphics.Bitmap
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView.OnItemClickListener
import android.widget.ArrayAdapter
import android.widget.GridView
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.collection.LruCache
import com.pspdfkit.catalog.R
import com.pspdfkit.catalog.SdkExample
import com.pspdfkit.catalog.SdkExample.Companion.TAG
import com.pspdfkit.configuration.activity.PdfActivityConfiguration
import com.pspdfkit.document.DocumentSource
import com.pspdfkit.document.PdfDocument
import com.pspdfkit.document.PdfDocumentLoader
import com.pspdfkit.document.providers.AssetDataProvider
import com.pspdfkit.ui.PdfActivityIntentBuilder
import com.pspdfkit.utils.Size
import com.pspdfkit.utils.getSupportParcelableExtra
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.FlowableEmitter
import io.reactivex.rxjava3.core.FlowableOnSubscribe
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.schedulers.Schedulers
import java.io.File
import java.io.IOException
import java.util.ArrayDeque
import java.util.Locale

/**
 * This example lists all documents found in the assets and presents them with their previews in a grid.
 */
class KioskExample(context: Context) : SdkExample(context, R.string.kioskExampleTitle, R.string.kioskExampleDescription) {
    override fun launchExample(context: Context, configuration: PdfActivityConfiguration.Builder) {
        val intent = Intent(context, KioskActivity::class.java)
        // Pass the configuration to our activity.
        intent.putExtra(KioskActivity.CONFIGURATION_ARG, configuration.build())
        context.startActivity(intent)
    }
}

/**
 * This activity displays all documents found in the assets folder of the app.
 */
class KioskActivity : AppCompatActivity() {
    private lateinit var configuration: PdfActivityConfiguration
    private var listAssetsDisposable: Disposable? = null

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

        setContentView(R.layout.activity_kiosk)

        configuration = intent.getSupportParcelableExtra(CONFIGURATION_ARG, PdfActivityConfiguration::class.java)?: throw NullPointerException("Extras bundle was missing configuration")

        val documentGrid = findViewById<GridView>(android.R.id.list)
        val documentAdapter: DocumentAdapter = DocumentAdapter(this)
        documentGrid.adapter = documentAdapter
        documentGrid.onItemClickListener =
            OnItemClickListener { _, _, position, _ ->
                val dataProvider =
                    documentAdapter.getItem(position)?.documentSource?.dataProvider?: return@OnItemClickListener

                // Open the touched document.
                val intent =
                    PdfActivityIntentBuilder.fromDataProvider(this@KioskActivity, dataProvider).configuration(configuration).build()
                startActivity(intent)
            }

        val progressBar = findViewById<ProgressBar>(android.R.id.progress)
        // Load the documents on a background thread.
        listAssetsDisposable =
            listAllAssets()
                // List assets on the background (I/O) thread..subscribeOn(Schedulers.io())
                // Filter PDF files only..filter { it.lowercase(Locale.getDefault()).endsWith(".pdf") }
                // The second observe on is necessary so opening the documents runs on a different thread as listing the assets..observeOn(Schedulers.io()).flatMap { asset ->
                    PdfDocumentLoader.openDocumentAsync(this@KioskActivity, DocumentSource(AssetDataProvider(asset))).toFlowable().doOnError { throwable ->
                            // This example catches any error that happens while opening the document (e.g. if a password would be needed).
                            // If an exception is thrown, the document will not be shown.
                            Log.w(
                                TAG,
                                String.format("Could not open document '%s' from assets. See exception for reason.", asset),
                                throwable,
                            )
                        }.onErrorResumeNext { Flowable.empty() }
                }.observeOn(AndroidSchedulers.mainThread()).doOnComplete { progressBar.visibility = View.GONE }.toSortedList { document: PdfDocument, document2: PdfDocument ->
                    val title = document.title
                    val title2 = document2.title
                    return@toSortedList when {
                        document === document2 -> 0
                        title == null -> -1
                        title2 == null -> 1
                        else -> title.compareTo(title2, ignoreCase = true)
                    }
                }.subscribe({ collection: List<PdfDocument> -> documentAdapter.addAll(collection) }) { throwable ->
                    progressBar.visibility = View.GONE
                    Log.e(TAG, "Error while trying to list all catalog app assets.", throwable)
                    Toast.makeText(
                        this@KioskActivity,
                        "Error listing asset files - see logcat for detailed error message.",
                        Toast.LENGTH_LONG,
                    ).show()
                }
    }

    override fun onDestroy() {
        super.onDestroy()

        listAssetsDisposable?.dispose()
        listAssetsDisposable = null
    }

    /**
     * Lists all assets in the assets directory.
     *
     * @return A observable sending all file paths in the assets folder.
     */
    private fun listAllAssets(): Flowable<String> {
        return Flowable.create<String>(
            object : FlowableOnSubscribe<String> {
                override fun subscribe(emitter: FlowableEmitter<String>) {
                    try {
                        val pathsToCheck = ArrayDeque<String>()
                        pathsToCheck.addAll(assets.list("")?: arrayOf())
                        while (!pathsToCheck.isEmpty()) {
                            val currentPath = pathsToCheck.poll()?: break
                            val children = getChildren(currentPath)
                            if (children.isNullOrEmpty()) {
                                // This is just a file, tell our subscriber about it.
                                emitter.onNext(currentPath)
                            } else {
                                // Check all other sub paths.
                                for (child in children) {
                                    pathsToCheck.add(currentPath + File.separator + child)
                                }
                            }
                        }
                        emitter.onComplete()
                    } catch (e: IOException) {
                        emitter.onError(e)
                    }
                }

                @Throws(IOException::class)
                private fun getChildren(path: String): Array<String> {
                    // Since listing assets is really really slow we assume everything with a '.' in it is a file.
                    return if (path.contains(".")) {
                        arrayOf()
                    } else {
                        assets.list(path)?: arrayOf()
                    }
                }
            },
            BackpressureStrategy.BUFFER,
        )
    }

    private class ViewHolder(val view: View) {
        val itemPreviewImageView: ImageView = view.findViewById(R.id.itemPreviewImageView)
        val itemTitleView: TextView = view.findViewById(R.id.itemTileView)

        var previewRenderDisposable: Disposable? = null

        companion object {
            operator fun get(convertView: View?, parent: ViewGroup): ViewHolder {
                var view = convertView
                val holder: ViewHolder
                if (view!= null) {
                    holder = view.tag as ViewHolder
                } else {
                    view = LayoutInflater.from(parent.context).inflate(R.layout.item_kiosk_item, parent, false)
                    holder = ViewHolder(view)
                    view.tag = holder
                }
                return holder
            }
        }
    }

    private inner class DocumentAdapter(context: Context) : ArrayAdapter<PdfDocument>(context, View.NO_ID) {
        private val previewImageCache: LruCache<String, Bitmap>
        private val previewImageSize: Size

        override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
            val holder: ViewHolder = ViewHolder[convertView, parent]
            holder.previewRenderDisposable?.dispose()

            val document = getItem(position)?: throw IllegalStateException("Invalid position!")

            // We only want to render a new preview image if we don't already have one in the cache.
            val cachedPreview = previewImageCache[document.uid]
            holder.itemPreviewImageView.setImageBitmap(cachedPreview)
            if (cachedPreview == null) {
                // Calculate the size of the rendered preview image.
                val size = calculateBitmapSize(document, previewImageSize)

                // Render page to bitmap.
                holder.previewRenderDisposable =
                    document.renderPageToBitmapAsync(
                            parent.context,
                            0,
                            size.width.toInt(),
                            size.height.toInt(),
                        ).observeOn(AndroidSchedulers.mainThread()).subscribe { bitmap ->
                            holder.itemPreviewImageView.setImageBitmap(bitmap)
                            previewImageCache.put(document.uid, bitmap)
                        }
            }

            if (!TextUtils.isEmpty(document.title)) {
                holder.itemTitleView.text = document.title
            } else {
                holder.itemTitleView.text = resources.getText(com.pspdfkit.R.string.pspdf__activity_title_unnamed_document)
            }

            return holder.view
        }

        private fun calculateBitmapSize(document: PdfDocument, availableSpace: Size): Size {
            val pageSize = document.getPageSize(0)
            val ratio: Float
            ratio =
                if (pageSize.width > pageSize.height) {
                    availableSpace.width / pageSize.width
                } else {
                    availableSpace.height / pageSize.height
                }
            return Size(pageSize.width * ratio, pageSize.height * ratio)
        }

        init {
            previewImageCache =
                object : LruCache<String, Bitmap>((Runtime.getRuntime().maxMemory() / 1024 / 8).toInt()) {
                    override fun sizeOf(key: String, value: Bitmap): Int {
                        // The cache size will be measured in kilobytes rather than number of items.
                        return value.byteCount / 1024
                    }
                }
            previewImageSize =
                Size(
                    context.resources.getDimensionPixelSize(R.dimen.kiosk_previewimage_width).toFloat(),
                    context.resources.getDimensionPixelSize(R.dimen.kiosk_previewimage_height).toFloat(),
                )
        }
    }

    companion object {
        const val CONFIGURATION_ARG = "configuration"
    }
}

```

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)
- [Search Indexing](/guides/android/samples/search-indexing-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)

