This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /guides/android/samples/construction-floor-plan-kotlin.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. Construction Floor Plan

Display a building floor plan with custom pin stamps, hide/show annotations, and a custom toolbar.


/*
* Copyright © 2022-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.
*/
@file:Suppress("DEPRECATION")
package com.pspdfkit.catalog.examples.kotlin
import android.content.Context
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.graphics.drawable.toBitmap
import androidx.lifecycle.lifecycleScope
import com.pspdfkit.annotations.Annotation
import com.pspdfkit.annotations.AnnotationFlags
import com.pspdfkit.annotations.AnnotationType
import com.pspdfkit.annotations.actions.HideAction
import com.pspdfkit.annotations.configuration.StampAnnotationConfiguration
import com.pspdfkit.annotations.stamps.StampPickerItem
import com.pspdfkit.catalog.R
import com.pspdfkit.catalog.tasks.ExtractAssetTask
import com.pspdfkit.compose.toolbar.state.ToolbarIcon
import com.pspdfkit.compose.toolbar.state.ToolbarItem
import com.pspdfkit.compose.toolbar.state.ToolbarItemState
import com.pspdfkit.configuration.activity.PdfActivityConfiguration
import com.pspdfkit.document.PdfDocument
import com.pspdfkit.jetpack.compose.toolbar.nutrientToolbarCoordinator
import com.pspdfkit.jetpack.compose.toolbar.setMenuItemGroupingRule
import com.pspdfkit.ui.PdfActivity
import com.pspdfkit.ui.PdfActivityIntentBuilder
import com.pspdfkit.ui.annotations.OnAnnotatingModeChangeListener
import com.pspdfkit.ui.annotations.OnAnnotationSelectedListener
import com.pspdfkit.ui.special_mode.controller.AnnotatingController
import com.pspdfkit.ui.special_mode.controller.AnnotationSelectionController
import com.pspdfkit.ui.toolbar.grouping.presets.MenuItem
import com.pspdfkit.ui.toolbar.grouping.presets.PresetMenuItemGroupingRule
import kotlinx.coroutines.launch
class ConstructionExample(context: Context) :
AssetExample(
context,
R.string.constructionExampleTitle,
R.string.constructionExampleDescription,
) {
override val assetPath: String = "Floor Plan.pdf"
override fun prepareConfiguration(configuration: PdfActivityConfiguration.Builder) {
super.prepareConfiguration(configuration)
configuration.autosaveEnabled(false)
// turn off the display of the note icon for annotations with an attached note
configuration.setAnnotationNoteHintingEnabled(false)
// Apply a custom theme which overrides the icon for the stamp toolbar button in the annotation creation toolbar
configuration.theme(R.style.PSPDFCatalog_Theme_ConstructionExample)
// Turn on measurements. This is on by default if you have it in your license.
configuration.setMeasurementToolsEnabled(true)
}
override fun launchExample(context: Context, configuration: PdfActivityConfiguration.Builder) {
prepareConfiguration(configuration)
// Since Nutrient does not directly read documents from the assets, we extract them
// to the internal device storage using a custom AsyncTask implementation.
ExtractAssetTask.extract(assetPath, title, context) { documentFile ->
// Now, as the PDF document file is sitting in the internal device storage, we can
// start the ConstructionExampleActivity activity by passing it the Uri of the file.
val intent =
PdfActivityIntentBuilder
.fromUri(context, Uri.fromFile(documentFile))
.activityClass(ConstructionExampleActivity::class.java)
.configuration(configuration.build())
.build()
context.startActivity(intent)
}
}
}
class ConstructionExampleActivity :
PdfActivity(),
OnAnnotationSelectedListener,
OnAnnotatingModeChangeListener {
var annotationsHidden = false
val desiredAnnotationTypes =
AnnotationType.entries.toMutableSet().apply {
// ignore LINK annotations
remove(AnnotationType.LINK)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Register the grouping rule that tells the annotation creation toolbar how to group its
// menu items. This uses the deprecated MenuItemGroupingRule bridge so existing
// PresetMenuItemGroupingRule subclasses keep working; new code should implement
// ContextualToolbarGroupingProvider and register it with
// nutrientToolbarCoordinator().setContextualToolbarGroupingProvider(...) instead.
nutrientToolbarCoordinator().setMenuItemGroupingRule(
this,
CustomAnnotationCreationToolbarGroupingRule(this),
)
// Customize the main toolbar declaratively via the Compose-toolbar API. This replaces the
// legacy onGenerateMenuItemIds / onCreateOptionsMenu / onPrepareOptionsMenu /
// onOptionsItemSelected overrides. We prepend our custom show/hide items, remove the
// thumbnail-grid default, and toggle each item's visibility based on the hidden state.
setToolbarItemsProvider { defaultItems ->
val hideItem =
ToolbarItem.Button(
actionId = "custom_action_hide",
id = R.id.custom_action_hide,
contentDescription = "Hide",
icon = ToolbarIcon.Resource(R.drawable.ic_hide),
state = ToolbarItemState(isVisible = !annotationsHidden),
overflowBehavior = ToolbarItem.OverflowBehavior.IfRoom,
)
val showItem =
ToolbarItem.Button(
actionId = "custom_action_show",
id = R.id.custom_action_show,
contentDescription = "Show",
icon = ToolbarIcon.Resource(R.drawable.ic_show),
state = ToolbarItemState(isVisible = annotationsHidden),
overflowBehavior = ToolbarItem.OverflowBehavior.IfRoom,
)
// Prepend our two custom items and drop the thumbnail-grid default item.
listOf(hideItem, showItem) + defaultItems.filterNot { it.id == MENU_OPTION_THUMBNAIL_GRID }
}
// Handle clicks on our custom show/hide toolbar items.
setOnToolbarItemClickListener { id ->
when (id) {
R.id.custom_action_hide,
R.id.custom_action_show,
-> {
// Show/hide annotations accordingly and refresh the toolbar.
annotationsHidden = !annotationsHidden
setAnnotationVisibility(annotationsHidden)
invalidateToolbarItems()
true
}
else -> false
}
}
}
override fun onDocumentLoaded(document: PdfDocument) {
super.onDocumentLoaded(document)
// Check if we have any hidden annotation. If so, we should show the "show" item in the options menu
lifecycleScope.launch {
val hasHiddenAnnotations =
document.annotationProvider.getAllAnnotationsOfType(desiredAnnotationTypes).firstOrNull {
it.hasFlag(AnnotationFlags.HIDDEN)
} != null
if (hasHiddenAnnotations != annotationsHidden) {
annotationsHidden = hasHiddenAnnotations
invalidateToolbarItems()
}
}
setupPinStamp()
}
/**
* Create a custom configuration for Stamp annotations which creates a Pin annotation.
* This can be achieved by setting a single StampPickerItem (which visualizes a pin in our case) to the configuration,
* which leads to a behavior change in the annotation creation. Instead of showing a stamp picker dialog, the stamp
* annotation is created immediatly from this only item.
*/
private fun setupPinStamp() {
val fragment = requirePdfFragment()
val pinBitmap =
AppCompatResources.getDrawable(this, R.drawable.ic_pin_drop)?.toBitmap() ?: return
// First we create our custom StampPickerItem
val pinStamp =
StampPickerItem
.fromBitmap(pinBitmap)
.withSize(50f, 50f)
.build()
// Then put it into a StampAnnotationConfiguration
val stampConfig =
StampAnnotationConfiguration
.builder(this)
// Here we return list of stamp picker items that are going to be available in the stamp picker.
.setAvailableStampPickerItems(listOf(pinStamp))
.build()
// Replace the default StampAnnotationConfiguration with our custom one
fragment
.annotationConfiguration
.put(AnnotationType.STAMP, stampConfig)
// When creating such a pin stamp we always want to add a note to it.
// To spare the user the extra click for adding a note, we invoke the NoteEditor automatically
// Therefore we need two listeners which inform us when an annotation is created and when
// the editing mode is invoked (both events happen right one after the other)
fragment.addOnAnnotationSelectedListener(this)
fragment.addOnAnnotatingModeChangeListener(this)
}
// add or remove the HIDDEN flag to the annotations to achieve the hide/show effect
private fun setAnnotationVisibility(shouldHide: Boolean) {
val doc = document ?: return
lifecycleScope.launch {
val annotations =
doc
.annotationProvider
.getAllAnnotationsOfType(desiredAnnotationTypes)
pdfFragment?.executeAction(HideAction(annotations, null, shouldHide))
}
}
private var newPinAnnotationCreated = false
//region OnAnnotationSelectedListener
override fun onPrepareAnnotationSelection(
controller: AnnotationSelectionController,
annotation: Annotation,
annotationCreated: Boolean,
): Boolean {
// since our custom pin is the only stamp that we create in this example we can just use the type for detection
newPinAnnotationCreated = annotationCreated && annotation.type == AnnotationType.STAMP
return true
}
override fun onAnnotationSelected(annotation: Annotation, annotationCreated: Boolean) {}
//endregion
//region OnAnnotatingModeChangeListener
override fun onEnterAnnotatingMode(controller: AnnotatingController) {
// in case the last annotation selection was in context with a pin creation, invoke the note editor
if (newPinAnnotationCreated && controller.hasCurrentlySelectedAnnotations()) {
controller.currentSingleSelectedAnnotation?.let {
controller.showAnnotationEditor(it)
}
}
}
override fun onChangeAnnotatingMode(controller: AnnotatingController) {}
override fun onExitAnnotatingMode(controller: AnnotatingController) {}
//endregion
/**
* Class that implements the [PresetMenuItemGroupingRule], used to tell the toolbar how to
* group menu items in the toolbar.
*
* @see com.pspdfkit.catalog.ui.CustomAnnotationCreationToolbarGroupingRule for more details.
*/
private class CustomAnnotationCreationToolbarGroupingRule constructor(context: Context) : PresetMenuItemGroupingRule(context) {
companion object {
private const val LOW_CAPACITY_ITEMS_COUNT = 5
private const val HIGH_CAPACITY_ITEMS_COUNT = 7
}
private val lowCapacityItemsGrouping: MutableList<MenuItem> = ArrayList(LOW_CAPACITY_ITEMS_COUNT)
private val highCapacityItemsGrouping: MutableList<MenuItem> = ArrayList(HIGH_CAPACITY_ITEMS_COUNT)
override fun getGroupPreset(capacity: Int, itemsCount: Int): List<MenuItem> = when {
capacity >= HIGH_CAPACITY_ITEMS_COUNT -> highCapacityItemsGrouping
capacity >= LOW_CAPACITY_ITEMS_COUNT -> lowCapacityItemsGrouping
// in case we don't have enough capacity, return an empty list
else -> ArrayList(capacity)
}
override fun areGeneratedGroupItemsSelectable(): Boolean = true
init {
lowCapacityItemsGrouping.addAll(
listOf(
MenuItem(
com.pspdfkit.R.id.pspdf__annotation_toolbar_group_drawing,
intArrayOf(
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_line,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_line_arrow,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_ink_pen,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_magic_ink,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_freetext,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_freetext_callout,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_note,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_measurement_distance,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_measurement_perimeter,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_measurement_area_polygon,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_measurement_area_rect,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_measurement_area_ellipse,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_measurement_scale_calibration,
),
),
MenuItem(
com.pspdfkit.R.id.pspdf__annotation_toolbar_group_markup,
intArrayOf(
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_cloudy_square,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_cloudy_circle,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_dashed_square,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_dashed_circle,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_square,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_circle,
),
),
MenuItem(com.pspdfkit.R.id.pspdf__annotation_toolbar_item_image),
MenuItem(com.pspdfkit.R.id.pspdf__annotation_toolbar_item_stamp),
MenuItem(com.pspdfkit.R.id.pspdf__annotation_toolbar_item_picker),
MenuItem(
com.pspdfkit.R.id.pspdf__annotation_toolbar_group_undo_redo,
intArrayOf(
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_undo,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_redo,
),
),
),
)
highCapacityItemsGrouping.addAll(
listOf(
MenuItem(
com.pspdfkit.R.id.pspdf__annotation_toolbar_group_measurement,
intArrayOf(
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_measurement_distance,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_measurement_perimeter,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_measurement_area_polygon,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_measurement_area_rect,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_measurement_area_ellipse,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_measurement_scale_calibration,
),
),
MenuItem(
com.pspdfkit.R.id.pspdf__annotation_toolbar_group_drawing,
intArrayOf(
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_line,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_line_arrow,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_ink_pen,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_magic_ink,
),
),
MenuItem(
com.pspdfkit.R.id.pspdf__annotation_toolbar_group_markup,
intArrayOf(
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_cloudy_square,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_cloudy_circle,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_dashed_square,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_dashed_circle,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_square,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_circle,
),
),
MenuItem(
com.pspdfkit.R.id.pspdf__annotation_toolbar_group_writing,
intArrayOf(
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_freetext,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_freetext_callout,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_note,
),
),
MenuItem(com.pspdfkit.R.id.pspdf__annotation_toolbar_item_image),
MenuItem(com.pspdfkit.R.id.pspdf__annotation_toolbar_item_stamp),
MenuItem(com.pspdfkit.R.id.pspdf__annotation_toolbar_item_picker),
MenuItem(
com.pspdfkit.R.id.pspdf__annotation_toolbar_group_undo_redo,
intArrayOf(
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_undo,
com.pspdfkit.R.id.pspdf__annotation_toolbar_item_redo,
),
),
),
)
}
}
}

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