Skip to content

Monitor Uploads

Alex Gotev edited this page Jun 10, 2021 · 30 revisions

Index

When you make an UploadRequest, you get the notification and the progress displayed for free, but in many cases you need to display progress also in your app screens. This is achieved using the RequestObserver.

The RequestObserver

The RequestObserver is a lifecycle-aware BroadcastReceiver which receives events coming from single or multiple upload requests. It can be used inside Activity or Fragment. Inside a Service or globally in your app you have to use GlobalRequestObserver, depending on your specific needs.

Lifecycle-aware means that once you attach the RequestObserver to a LifecycleOwner, it will honor its lifecycle. So, for example if you attach it to an Activity, it will be around while the activity is alive. It will stop receiving events when the activity gets paused and it will restart receiving events when it gets resumed again. If the upload completes while you are not observing (either with success or error), onCompletedWhileNotObserving will be invoked, so you have a chance to do something in response. When the Activity dies, the RequestObserver dies as well and it won't receive any other event afterwards.

Both RequestObserver and GlobalRequestObserver auto registers themselves once created for convenience. RequestObserver takes care of automatically unregister itself once the lifecycle in which is created gets destroyed. GlobalRequestObserver does not auto unregister itself automatically, so it keeps listening for events, but you can call unregister() and then register() again on its instance based on your specific needs.

If you are coming from 3.x, this is the substitute for BroadcastReceiver and Delegate and it can be used in the same places you are already using it, in almost the same way.

Rule of thumb to monitor uploads

You start an upload somewhere in your app. Where do you need the server result?

  • Only in the UI which started the upload: use RequestObserver or .subscribe the request
  • In many different UIs or at app level: use GlobalRequestObserver

Subscribing to a single upload request

The easiest and most concise way of monitoring an upload is to subscribe to it. In this way the RequestObserver will receive events only from the subscribed upload request in the lifecycle of the UI which launched the upload. Simple example with a MultipartUploadRequest. It works the same also with the other request types.

fun upload(context: Context, lifecycleOwner: LifecycleOwner) {
    MultipartUploadRequest(this, "https://server.url")
        .addFileToUpload(filePath = "/path/to/file", parameterName = "myFile")
        .subscribe(context = context, lifecycleOwner = lifecycleOwner, delegate = object : RequestObserverDelegate() {
            override fun onProgress(context: Context, uploadInfo: UploadInfo) {
                // do your thing
            }

            override fun onSuccess(
                context: Context,
                uploadInfo: UploadInfo,
                serverResponse: ServerResponse
            ) {
                // do your thing
            }

            override fun onError(
                context: Context,
                uploadInfo: UploadInfo,
                exception: Throwable
            ) {
                when (exception) {
                    is UserCancelledUploadException -> {
                        Log.e("RECEIVER", "Error, user cancelled upload: $uploadInfo")
                    }

                    is UploadError -> {
                        Log.e("RECEIVER", "Error, upload error: ${exception.serverResponse}")
                    }

                    else -> {
                        Log.e("RECEIVER", "Error: $uploadInfo", exception)
                    }
                }
            }

            override fun onCompleted(context: Context, uploadInfo: UploadInfo) {
                // do your thing
            }

            override fun onCompletedWhileNotObserving() {
                // do your thing
            }
        })
}

From Activity

Supposing you're using the above example function.

class MyActivity : AppCompatActivity() {
   fun myAction() {
       upload(context = this, lifecycleOwner = this)
   }

   fun upload(context: Context, lifecycleOwner: LifecycleOwner) { ... }
}

From Fragment

Supposing you're using the above example function.

class MyFragment: Fragment() {
    fun myAction() {
        upload(context = requireContext(), lifecycleOwner = viewLifecycleOwner)
    }

    fun upload(context: Context, lifecycleOwner: LifecycleOwner) { ... }
}

From Service

Supposing you're using the above example function.

class MyService : LifecycleService() {
    fun myAction() {
        upload(context = this, lifecycleOwner = this)
    }

    fun upload(context: Context, lifecycleOwner: LifecycleOwner) { ... }
}

Subscribing to all upload requests

Do this when you need to perform some custom logic for all the uploads. The following example shows how to do it globally in your app:

class App : Application() {
    override fun onCreate() {
        super.onCreate()

        GlobalRequestObserver(this, GlobalUploadObserver())
    }
}

GlobalUploadObserver.kt

class GlobalUploadObserver : RequestObserverDelegate {
    override fun onProgress(context: Context, uploadInfo: UploadInfo) {
        Log.e("RECEIVER", "Progress: $uploadInfo")
    }

    override fun onSuccess(context: Context, uploadInfo: UploadInfo, serverResponse: ServerResponse) {
        Log.e("RECEIVER", "Success: $serverResponse")
    }

    override fun onError(context: Context, uploadInfo: UploadInfo, exception: Throwable) {
        when (exception) {
            is UserCancelledUploadException -> {
                Log.e("RECEIVER", "Error, user cancelled upload: $uploadInfo")
            }

            is UploadError -> {
                Log.e("RECEIVER", "Error, upload error: ${exception.serverResponse}")
            }

            else -> {
                Log.e("RECEIVER", "Error: $uploadInfo", exception)

            }
        }
    }

    override fun onCompleted(context: Context, uploadInfo: UploadInfo) {
        Log.e("RECEIVER", "Completed: $uploadInfo")
    }

    override fun onCompletedWhileNotObserving() {
        Log.e("RECEIVER", "Completed while not observing")
    }
}

Subscribing to specific upload requests

When you need to create a RequestObserver for a specific upload you launched somewhere else in your code or a subset of all of your uploads, you can accept only events coming from specific uploads.

RequestObserver(context, lifecycleOwner, delegate, shouldAcceptEventsFrom = { uploadInfo ->
    // implement your custom business logic here. 
    // return true to accept events from this uploadInfo, false otherwise
})

Example 1

You want to listen to events coming from a specific uploadID.

You launched an upload request from somewhere in your code (e.g. your main fragment), for example:

val myUploadID = MultipartUploadRequest(context, serverUrl = "https://my.server.com")
    .setMethod("POST")
    .addFileToUpload(
        filePath = filePath,
        parameterName = "myFile"
    ).startUpload()

And you want to observe that same upload from somewhere else in your UI (e.g. a detail fragment). It's up to you to decide in which way to store and retrieve myUploadID across your app.

This is how you can observe events coming from myUploadID anywhere in your UI code:

RequestObserver(context, lifecycleOwner, delegate, shouldAcceptEventsFrom = { uploadInfo ->
    uploadInfo.uploadId == myUploadID
})

The delegate will receive only the events coming from myUploadID.

Example 2

You have multiple categories of uploads: football, basketball, baseball.

Leveraging UploadRequest

.setUploadID("football-${UUID.randomUUID().toString()}")

method you can assign a custom unique ID to all the football uploads and use that information to make a football-only observer, like this:

RequestObserver(context, lifecycleOwner, delegate, shouldAcceptEventsFrom = { uploadInfo ->
    uploadInfo.uploadId.startsWith("football")
})