E-Learning Example
Shows how to swap between documents and sync view state, annotations, and bookmarks.
/* * 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.Contextimport android.net.Uriimport android.os.Bundleimport android.view.Menuimport android.view.MenuItemimport androidx.annotation.UiThreadimport androidx.lifecycle.lifecycleScopeimport com.pspdfkit.annotations.AnnotationTypeimport com.pspdfkit.bookmarks.Bookmarkimport com.pspdfkit.catalog.Rimport com.pspdfkit.catalog.SdkExampleimport com.pspdfkit.catalog.tasks.ExtractAssetTask.extractimport com.pspdfkit.configuration.activity.PdfActivityConfigurationimport com.pspdfkit.configuration.page.PageLayoutModeimport com.pspdfkit.configuration.sharing.ShareFeaturesimport com.pspdfkit.document.PdfDocumentimport com.pspdfkit.preferences.PSPDFKitPreferencesimport com.pspdfkit.ui.DocumentDescriptorimport com.pspdfkit.ui.PdfActivityimport com.pspdfkit.ui.PdfActivityIntentBuilderimport com.pspdfkit.ui.special_mode.controller.AnnotationToolimport com.pspdfkit.utils.getSupportParcelableExtraimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.Jobimport kotlinx.coroutines.launchimport kotlinx.coroutines.withContextimport java.util.EnumSet
/** * This example shows how to swap between documents and sync view state, annotations, and bookmarks. */class ELearningExample(context: Context) : SdkExample(context, R.string.eLearningExampleTitle, R.string.eLearningExampleDescription) { override fun launchExample(context: Context, configuration: PdfActivityConfiguration.Builder) { // We'll disable redaction tool in annotation creation toolbar to prevent creation of redaction annotations. val annotationTools = mutableListOf(*AnnotationTool.values()) annotationTools.remove(AnnotationTool.REDACTION)
configuration // Turn off saving, so we have the clean original document every time the example is launched. .autosaveEnabled(false) // Use single page mode. .layoutMode(PageLayoutMode.SINGLE) // Disable all the menu items but annotations editing and bookmark list. .outlineEnabled(false) .documentInfoViewEnabled(false) .annotationListEnabled(false) .searchEnabled(false) .settingsMenuEnabled(false) .setEnabledShareFeatures(EnumSet.noneOf(ShareFeatures::class.java)) .printingEnabled(false) .thumbnailGridEnabled(false) .setRedactionUiEnabled(false) .enabledAnnotationTools(annotationTools)
// The annotation creator written into newly created annotations. If not set, or set to null // a dialog will normally be shown when creating an annotation, asking you to enter a name. // We are going to skip this part and set it as "John Doe" only if it was not yet set. if (!PSPDFKitPreferences.get(context).isAnnotationCreatorSet) { PSPDFKitPreferences.get(context).setAnnotationCreator("John Doe") } // Extracts the documents from the assets and loads the teacher version first. extract("Teacher.pdf", title, context) { teacherDocumentFile -> extract("Student.pdf", title, context) { studentDocumentFile -> val teacherDocumentUri = Uri.fromFile(teacherDocumentFile) val studentDocumentUri = Uri.fromFile(studentDocumentFile) val intent = PdfActivityIntentBuilder.fromUri(context, teacherDocumentUri) .configuration(configuration.build()) .activityClass(ELearningActivity::class) .build() intent.putExtra(ELearningActivity.STUDENT_URI_KEY, studentDocumentUri) intent.putExtra(ELearningActivity.TEACHER_URI_KEY, teacherDocumentUri) context.startActivity(intent) } } }}
class ELearningActivity : PdfActivity() {
/** List of annotations in instant JSON format that will be copied over the new document. */ private val serializedAnnotationsToTransfer: MutableList<String> = mutableListOf()
/** List of bookmarks that will be copied over the new document. */ private val bookmarksToTransfer: MutableList<Bookmark> = mutableListOf()
/** Student document URI. */ private lateinit var studentUri: Uri
/** Teacher document URI. */ private lateinit var teacherUri: Uri
/** Job in charge of switching the document. */ private var switchDocumentJob: Job? = null
/** Job in charge of loading the annotation to the new document. */ private var transferDataJob: Job? = null
/** Fragment state containing current page, zoom, and scroll. */ private var fragmentState: Bundle? = null
/** Flag to keep track of visibility of bookmark list view. */ private var isBookmarkListDisplayed: Boolean = false
/** * Create a button to switch between teacher and student documents. */ override fun onCreateOptionsMenu(menu: Menu): Boolean { super.onCreateOptionsMenu(menu) if (document == null || serializedAnnotationsToTransfer.isNotEmpty()) { return false } // Add a new switch button. val switchItem = menu.add(0, SWITCH_ITEM_ID, 0, "Switch") switchItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM or MenuItem.SHOW_AS_ACTION_WITH_TEXT)
return true }
/** * Set the corresponding action for every button. */ override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { SWITCH_ITEM_ID -> { switchDocument() true } else -> { super.onOptionsItemSelected(item) } } }
override fun onPause() { switchDocumentJob?.cancel() transferDataJob?.cancel() super.onPause() }
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)
intent.getSupportParcelableExtra(STUDENT_URI_KEY, Uri::class.java)?.let { studentUri = it } intent.getSupportParcelableExtra(TEACHER_URI_KEY, Uri::class.java)?.let { teacherUri = it } }
@UiThread override fun onDocumentLoaded(document: PdfDocument) { // Take any serialized annotations that were stored upon switching, and add them to the loaded document. transferDataJob = lifecycleScope.launch { withContext(Dispatchers.IO) { // Create annotations from instant JSON and add them to the document. for (json in serializedAnnotationsToTransfer) { document.annotationProvider.createAnnotationFromInstantJson(json) } // Once all annotations are transferred, we can clear the list. serializedAnnotationsToTransfer.clear()
// Take any bookmarks that were stored upon switching, and add them to the loaded document. bookmarksToTransfer.forEach { bookmark -> document.bookmarkProvider.addBookmarkAsync(bookmark).blockingAwait() } // Once all bookmarks are transferred, we can clear the list. bookmarksToTransfer.clear() }
// Restore page index, scroll and zoom. fragmentState?.let { requirePdfFragment().state = it fragmentState = null }
// Make sure the outline view shows the changes from transferring bookmarks and annotations. pspdfKitViews.outlineView?.setDocument(document, configuration.configuration)
// Restore bookmark list view visibility. if (isBookmarkListDisplayed) { pspdfKitViews.outlineView?.show() }
invalidateOptionsMenu() } }
/** * Switch the document when the button is tapped. This works in multiple steps: * - Fetching all annotations asynchronously, and convert them to Instant JSON format. * - Fetch all bookmarks. * - Save the view state. */ private fun switchDocument() { val document = document ?: return
// While the document switching is in progress, we disable all document interaction to // prevent changes to be made. requirePdfFragment().isDocumentInteractionEnabled = false
// Run the collection of annotations and bookmarks asynchronously. switchDocumentJob = lifecycleScope.launch { // Clear previous data serializedAnnotationsToTransfer.clear() bookmarksToTransfer.clear()
withContext(Dispatchers.IO) { // Fetch all annotations, serialize them, and collect them in a list. We keep them around in // memory, and will add them to the other document once it is loaded. val annotations = document.annotationProvider.getAllAnnotationsOfType( AnnotationType.entries.toSet() )
annotations.forEach { annotation -> val json = annotation.toInstantJson() // For some unsupported annotation types (like popup annotations) we don't offer serialization. // In these cases, `toInstantJson()` returns "null". We filter those from the serialized items. if (invalidInstantJson(json)) { serializedAnnotationsToTransfer.add(json) } }
// Fetch the bookmarks for synchronization and collect them in a list. val bookmarks = document.bookmarkProvider.bookmarks bookmarksToTransfer.addAll(bookmarks) }
// Save current page index, scroll and zoom. fragmentState = requirePdfFragment().state
// Save bookmark list view visibility. isBookmarkListDisplayed = pspdfKitViews.outlineView?.isDisplayed ?: false
// Swap out the current document to either the teacher or student version. if (document.documentSource.fileUri == teacherUri) { documentCoordinator.setDocument(DocumentDescriptor.fromUri(studentUri)) } else { documentCoordinator.setDocument(DocumentDescriptor.fromUri(teacherUri)) } invalidateOptionsMenu() } }
companion object { private const val SWITCH_ITEM_ID = 1234 const val STUDENT_URI_KEY = "TeacherStudentActivity.STUDENT_URI_KEY" const val TEACHER_URI_KEY = "TeacherStudentActivity.TEACHER_URI_KEY" }}
private fun invalidInstantJson(it: String) = it != "null"This code sample is an example that illustrates how to use our SDK. Please adapt it to your specific use case.