New: Create and edit Word documents with DOCX Editor in WebViewer

How to Build an Android App for Document Collaboration with Firebase

By Shirley Gong | 2019 Apr 30

Sanity Image
Read time

5 min

In this tutorial, you will learn how to create a real-time document collaboration app using the Apryse Android SDK and Firebase. This will let many users view the same PDF, Office or image file at the same time across their Android devices and communicate via comments, highlights, signatures and other annotations.

The full source code for this article can be found here. And the same steps will work similarly with any backend, whether that's the Apryse WebViewer server or your own server.

Apryse Document Collaboration in Android App

Users discussing the same document in real time via the Apryse DK and Firebase

Background and Further Resources

Firebase began in 2011 as a humble API allowing integration of online chat into a website. But Firebase founders James Tamplin and Andrew Lee soon discovered their solution could pass along a lot more than just chat messages.

Today, Firebase provides developers an API to store and sync almost any type of online data. And by taking care of backend functions like web hosting, authentication, usage tracking, and more, Firebase serves as one way to expedite development of scalable cloud-based apps. (Today, the company claims more than 1.5 million applications use Firebase products.)

There is great potential synergy between Firebase and the cross-platform Apryse SDK. So we created a drop-in sample to show how the two could work together to enable real-time online collaboration and discussion. What follows are the sample instructions for your Android project. (The accompanying WebViewer documentation is available here in case you'd like to integrate a web application.) Once you've got your sample project up and running, there is a customization guide that will show you how to customize the open source UI. You can also see and learn more about Apryse's customizable document annotation tools and extensible annotation functionality here.

Step 1 - Add Firebase to Your Android Project

Firebase has a very comprehensive tutorial on how to add Firebase to your Android project. Head here to get your project set up. Option 1 Add Firebase using the Firebase console is recommended. By the end of this step, your Firebase project console will recognize your Android app.

Next, let's add two additional packages: Firebase Authentication and Firebase Realtime Database. In your app module's build.gradle file (usually app/build.gradle), add the following:

dependencies {
    implementation ''
    // Add the authentication and realtime database dependencies
    implementation ''
    implementation ''

Step 2 - Add Apryse Android SDK to your Android project

You will now add three Apryse Android packages to your app. For simplicity, we will use Gradle integration.

First, head over to our Gradle integration guide to see how to add the Apryse Package and Tools Package to your Android project.

Then, in your app module's build.gradle file (usually app/build.gradle), add the following:

android {
    defaultConfig {
        minSdkVersion 21
    // Add Java 8
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8

dependencies {
    implementation "com.pdftron:pdftron:9.4.1"
    implementation "com.pdftron:tools:9.4.1"
    // Add PDFTron collaboration package
    implementation "com.pdftron:collab:9.4.1"

Step 3 - Implement Server Functionality


First, go to the Firebase console and click the "Authentication" button on the left panel and then click the "Sign-in Method" tab, just to the right of "Users". From this page click the "Anonymous" button and choose to enable anonymous login.

Then, create a Server.kt file in your project and add...

class Server(applicationContext: Context) : CustomService {

    private var mDatabase: CollabDatabase? = null

    init {
        mDatabase = CollabDatabase.getInstance(applicationContext)

    private lateinit var mBroadcaster: FlowableEmitter<ServerEvent>
    private var mFlowableDisposable: Disposable? = null
    private val mFlowable = Flowable.create(
        FlowableOnSubscribe<ServerEvent> { emitter -> mBroadcaster = emitter },
    private var mDisposables: CompositeDisposable = CompositeDisposable()

    private var auth: FirebaseAuth = FirebaseAuth.getInstance()
    private var annotationsRef: DatabaseReference? = null
    private var authorsRef: DatabaseReference? = null

    fun signIn(): Flowable<ServerEvent> {
        mFlowableDisposable = mFlowable.subscribe()

            .addOnCompleteListener {
                mBroadcaster.onNext(ServerEvent.SignIn(it)) // broadcast to UI to obtain user name

        return mFlowable

    private fun initDB() {
        val database = FirebaseDatabase.getInstance()
        annotationsRef = database.getReference("annotations")
        authorsRef = database.getReference("authors")

Annotation Data Flow

After successfully authenticating the user and having obtained a user name, let's add the user to the Firebase database and start subscribing to database changes.

fun updateAuthor(authorName: String) {
    if (auth.currentUser != null && authorsRef != null && annotationsRef != null) {
        val authorId = auth.currentUser!!.uid

        // add user and sample document
        mDisposables.add(addUserAndDocument(authorId, authorName).subscribeOn(


        // subscribe to authors

        // subscribe to annotations
        annotationsRef!!.addListenerForSingleValueEvent(object: ValueEventListener {
            override fun onCancelled(p0: DatabaseError) {

            override fun onDataChange(p0: DataSnapshot) {
                for (child in p0.children) {
                    val key = child.key
                    val newAnnot = child.getValue(
                    mInitialAnnotMap[key!!] = convAnnotationToAnnotationEntity(key, newAnnot!!)

Next, let's add functionality to send client annotation changes to Firebase. Apryse stores your annotations in XFDF — the ISO Standard Format for annotations interchange — which means your annotations can be preserved when sharing documents with people using other tools. Code is as follows:

private val OP_ADD = "add"
private val OP_MODIFY = "modify"
private val OP_REMOVE = "remove"

// CustomService start
override fun sendAnnotation(
    action: String?,
    annotations: ArrayList<AnnotationEntity>?,
    documentId: String?,
    userName: String?
) {
    if (Utils.isNullOrEmpty(action)) {
    for (entity in annotations!!) {
        val op =
        val annotId =
        val authorId = auth.currentUser!!.uid
        val xfdf = entity.xfdf
        when (op) {
            OP_ADD -> {
                val annotation = Annotation(
                createAnnotation(annotId, annotation)
            OP_MODIFY -> {
                val annotation = Annotation(
                changeAnnotation(annotId, annotation)
            OP_REMOVE -> {

Database Rules

Lastly, you should add server-side permission rules for writing data. Although client-side permission checking is supported in the SDK, every user has default access to each annotation's information (including authorId and authorName). Thus, data-write permissions should be regulated in the server as well. Add the following Database Rules to your Firebase console via the "Database" button on the left panel, and then click the "Rules" tab.

  "rules": {
    ".read": "auth != null",

    "annotations": {
      "$annotationId": {
        ".write": "auth.uid === newData.child('authorId').val() || auth.uid === data.child('authorId').val() || auth.uid === newData.child('parentAuthorId').val() || auth.uid === data.child('parentAuthorId').val()"

    "authors": {
      "$authorId": {
        ".write": "auth.uid === $authorId"

Step 4 - Add the Viewer

You can now connect your server to the Apryse SDK's out-of-box collaboration UI, in your MainActivity.kt:

override fun onCreate(savedInstanceState: Bundle?) {

    server = Server(this.application)

    val documentViewModel = ViewModelProviders.of(this).get(

private fun setUpSampleView() {
    mPdfViewCtrlTabHostFragment = createPdfViewerFragment()

    mPdfViewCtrlTabHostFragment!!.addCollabHostListener(object :
        CollabPdfViewCtrlTabHostFragment.CollabTabHostListener {
        override fun onNavButtonPressed() {

        override fun onTabDocumentLoaded(p0: String?) {
                .subscribe {
                    if (it is ServerEvent.SignIn) {
                        if (it.response.isSuccessful) {
                            Log.d(TAG, "signInAnonymously:success")
                            // obtain user name and call server.updateAuthor

    val ft = supportFragmentManager.beginTransaction()
    ft.replace(, mPdfViewCtrlTabHostFragment!!, null)

Wrapping Up

That's it! Your real-time document collaboration app is ready to run. Moving on, head over to our UI customization guide, we will walk you through how to customize the collaboration UI.

If you have any questions about using Apryse SDK in your project, please feel to contact us and we will be happy to help!

The full source code for this article can be found here.

Sanity Image

Shirley Gong

Share this post