Skip to main content

Upload Files

This guide describes the methods to upload files to cloud storage.

Upload File#

The Files SDK provides four methods for uploading files from a mobile device to cloud storage. The main difference is that two of them accept Uri as a source file reference, while two others operate on the absolute local file path.

Methods which accept Uri as a source file:

fun enqueueFile(fileUri: Uri, destDirPath: String, overwriteMode: OverwriteMode)fun enqueueFile(fileUri: Uri, destDirPath: String, destName: String, overwriteMode: OverwriteMode)

Other two methods accept the absolute source file path:

fun enqueueFile(localPath: String, destDirPath: String, overwriteMode: OverwriteMode)fun enqueueFile(localPath: String, destDirPath: String, destName: String, overwriteMode: OverwriteMode)

The application usually calls the Uri version if it uses the SAF (Storage Access Framework) to select files. If the application has full access to the local storage and uses its own UI to select files, it can be easier to pass a direct file path through the localPath parameter.

Note: Methods which accept Uri, take persistable Uri permission with the flag Intent.FLAG_GRANT_READ_URI_PERMISSION on given Uri. Persistable permission is released when the file either: uploaded successfully or cancelled or fatal error encountered.

important

If the application passes an absolute path to a file and this file is located outside of the app-specific directory, then the application must have read access rights to the shared local storage. Otherwise, the file will not be uploaded to the cloud due to an access error.

All methods for adding files to the upload queue return an EnqueueResult object: an object containing a list of remote paths of the files added to the queue, remotePaths, as well as a list of information about the erroneous files, errorlist.

important

The list of errors errorList has the type List<Exception>, which does not allow you to directly get detailed information about the reason for the unsuccessful file enqueuing. In order to get this information, you have to check the list item for compliance with a specific error type. At the moment there is only one such type - InvalidFileNameException.

Upload Local File#

The following code example demonstrates adding a local file to the upload queue.

// The Uri of the file on the device.// App usually receives uri when it starts Intent.ACTION_OPEN_DOCUMENTval localFileUri: Uri = // ...
// Full path of destination directory in the cloud without file name.val destDirPath: String = "/remote"
// overwrite mode.val overwriteMode: OverwriteMode = FileUploader.OverwriteMode.OVERWRITE// NOT_OVERWRITE - Do not overwrite file on backend. If file exists error returned. */// OVERWRITE - Overwrite file on backend. User has allowed overwrite existing file.// UPDATE - Try to update file passing the current file version to backend. If file exists// on backend and its version differs from current one, new conflict file will be created on backend.
// ...
// Adds file to the upload queue. Note that method takes persistable Uri permission with flag// `Intent.FLAG_GRANT_READ_URI_PERMISSION`. Persistable permission will be released when file// either: uploaded successfully or cancelled or fatal error encountered.val result = FileManager.uploader.enqueueFile(    localFileUri,  // Uri (usually obtained via SAF) that denotes source file    destDirPath,   // Full path of destination directory in the cloud without file name    overwriteMode)

Links - FileUploader.OverwriteMode.

Upload Groups Of Local Files#

The following code example demonstrates adding a group of local file to the upload queue.

var disposable: Disposable? = nullvar currentPath = "/" // Full path of destination directory in the cloud without file name.
val filePaths: List<String> = listOf(/* paths to local files */)
// Adds list of files to the upload queue.// Method works asynchronously. It immediately returns Single which runs on IO thread and// resolves to the list of enqueue results containing remote paths which can be used to// monitor progress or cancel uploadings.disposable += FileManager.uploader.enqueueFilesFromPaths(    filePaths,      // List of absolute paths to the local files (including file names) to be uploaded    currentPath,    // Full path of destination directory in the cloud without file name    FileUploader.OverwriteMode.OVERWRITE)    .flatMapObservable { enqueueResult ->        if (enqueueResult.errorList.isNotEmpty()) {            val ex = enqueueResult.errorList.first()            if (ex is InvalidFileNameException) {                /* Handle it */            } else {                /* Handle it */            }        }        Observable.fromIterable(enqueueResult.remotePaths)            .flatMap { remoteFilePath ->                FileManager.uploader.getItemObservable(remoteFilePath)                    .subscribeOn(AndroidSchedulers.mainThread())                    .doOnNext { Log.d(TAG, "Uploading progress $it") }                    .doOnComplete { Log.d(TAG, "Upload complete for $remoteFilePath") }                    .doOnError { Log.e(TAG, "Upload failed of $remoteFilePath", it) }            }    }    .observeOn(AndroidSchedulers.mainThread())    .subscribeBy(          onNext = { /* Handle it */ },          onError = { /* it: Throwable -> Handle it */ },          onComplete = { /* Handle it */ }    )
// You must call dispose() on disposable that returned by subscribe() method,// when it no longer needed, for example in your fragment’s onStop() or onPause().disposable?.dispose()

You can also use file uri list to upload files. To get a uri of files, you can use Storage Access Framework.

var disposable: Disposable? = nullvar currentPath = "/" // Full path of destination directory in the cloud without file name.const val REQUEST_CODE_UPLOAD_FILES = 202
// Storage Access Framework is used to get a list of uri files.private fun uploadSaf() {    val filePicker = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {        type = "*/*"        putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)        addCategory(Intent.CATEGORY_OPENABLE)    }    startActivityForResult(filePicker, REQUEST_CODE_UPLOAD_FILES)}
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {    super.onActivityResult(requestCode, resultCode, resultData)        if (resultCode != Activity.RESULT_OK) {          return    }      if (requestCode == REQUEST_CODE_UPLOAD_FILES) {        val uriList = resultData?.clipData?.let { clipData ->            // Multiple files were selected.            val uris = ArrayList<Uri>(clipData.itemCount)            for (i in 0 until clipData.itemCount) {                  uris.add(clipData.getItemAt(i).uri)            }            uris        } ?: resultData?.data?.let {            // Single file was selected.            listOf(it)        }                // Adds list of files to the upload queue. Note that method takes persistable Uri permission        // with flag `Intent.FLAG_GRANT_READ_URI_PERMISSION`. Persistable permission will be released        // when file either: uploaded successfully or cancelled or fatal error encountered.        //        // Method works asynchronously. It immediately returns Single which runs on IO thread and        // resolves to the list of enqueue results containing remote paths which can be used to        // monitor progress or cancel uploadings.        disposable += FileManager.uploader.enqueueFilesFromUris(            uriList,      // List of Uris (usually obtained via SAF) which are denote source files            currentPath,  // Full path of destination directory in the cloud without file name            FileUploader.OverwriteMode.OVERWRITE        )            .flatMapObservable { enqueueResult ->                if (enqueueResult.errorList.isNotEmpty()) {                    val ex = enqueueResult.errorList.first()                    if (ex is InvalidFileNameException) {                        /* Handle it */                    } else {                        /* Handle it */                    }                }                Observable.fromIterable(enqueueResult.remotePaths)                    .flatMap { remoteFilePath ->                        FileManager.uploader.getItemObservable(remoteFilePath)                            .subscribeOn(AndroidSchedulers.mainThread())                            .doOnNext { Log.d(TAG, "uploading in progress: $it") }                            .doOnComplete { Log.d(TAG, "uploading complete for $remoteFilePath") }                            .doOnError { Log.e(TAG, "Error uploading $remoteFilePath", it) }                    }            }.observeOn(AndroidSchedulers.mainThread())                .subscribeBy(                    onNext = { /* Handle it */ },                    onError = { /* it: Throwable -> Handle it */ },                    onComplete = { /* Handle it */ }                )    }}
override fun onPause() {    // You must call dispose() on disposable that returned by subscribe() method,    // when it no longer needed, for example in your fragment’s onStop() or onPause().    disposable?.dispose()}

The following code example demonstrates cancellation of uploading a group of local files.

var disposable: Disposable? = null
// ...
// Cancels files specified by remote paths.// Method works asynchronously and returns completable which completes when all files// are cancelled.disposable = FileManager.uploader.cancelFiles(    fullDestPathList // List of remote paths to cancel upload.)    .observeOn(AndroidSchedulers.mainThread())    .subscribeBy (        onComplete = { /* completion processing */ },        onError = { /* it: Throwable -> Handle it */ }    )
// ...
// You must call dispose() on disposable that returned by subscribe() method,// when it no longer needed, for example in your fragment’s onStop() or onPause().disposable?.dispose()

Links - FileUploader.OverwriteMode.

Upload File With Different Name#

This function allows to upload a file to the cloud with a different name. The following code example demonstrates adding a file to the upload queue.

// The Uri of the file on the device.// App usually receives uri when it starts Intent.ACTION_OPEN_DOCUMENTval localFileUri: Uri = // ...
// Destination full path without ending file name.val destDirPath: String = "/remote"
// Name of the file in the cloud.val destName: String = "new_file_name.txt"
// Full path of destination directory in the cloud without file name.val overwrite: OverwriteMode = FileUploader.OverwriteMode.OVERWRITE// NOT_OVERWRITE - Do not overwrite file on backend. If file exists error returned. */// OVERWRITE - Overwrite file on backend. User has allowed overwrite existing file.// UPDATE - Try to update file passing the current file version to backend. If file exists// on backend and its version differs from current one, new conflict file will be created on backend.
// ...
// Adds file to the upload queue. Note that method takes persistable Uri permission with flag// `Intent.FLAG_GRANT_READ_URI_PERMISSION`. Persistable permission will be released when file// either: uploaded successfully or cancelled or fatal error encountered.// Method returns result of the file enqueueing as well as others described above.FileManager.uploader.enqueueFile(    localFileUri,   // Uri (usually obtained via SAF) that denotes source file    destDirPath,    // Full path of destination directory in the cloud without file name    destName,       // New name of the file in the cloud    overwriteMode)

Links - FileUploader.OverwriteMode.

Invalid file names#

When uploading files, there is a set of restrictions on their names. An invalid file name will throw the InvalidFileNameException exception. For each constraint, there is the corresponding InvalidFileNameException.ErrorCode value:

  • INVALID_LENGTH - file name must be no more than 255 characters
  • STARTS_WITH_SPACE - file name must not start with a space
  • ENDS_WITH_SPACE - file name must not end with a space
  • ENDS_WITH_DOT - file name must not end with a period

Given the error code, an instance of the InvalidFileNameException class contains the following information:

  • code: ErrorCode - error code
  • uri: Uri? - URI of erroneous local file
  • path: String? - local path of erroneous file
  • fileName: String? - destination name of erroneous file

Either uri or path is set depending on how file was added to upload queue: with Uri or local path.

Links - InvalidFileNameException, InvalidFileNameException.ErrorCode.

Cancel Uploading#

To cancel uploading of a file, you need to remove a file from the uploading queue. The following code example demonstrates a cancellation of a file upload.

// Result of being uploaded.val result: EnqueueResult = FileManager.uploader        .enqueueFile(localFileUri, destDirPath, overwriteMode)
// ...
// Removes file from uploading queue.// If file is currently uploading - cancels upload.FileManager.uploader.cancelFile(    result.remotePath[0].path   // Full destination path of file in the cloud.)

If your app does not keep retuned destination path of the file upload, there is another option. App's UI usually keeps subscription on the current directory observable returned by FileManager.browser.getDirectoryContentObservable(currentPath, sortType) (see Get Directory Content). When a file is added to the uploading queue, app receives update with a new file list. That list contains the file which being uploaded (in spite of the file is not in the cloud yet). That allows user to interact with the file item in the list and tell the app to cancel the upload. In response to user action app can use FileItem.path to cancel file uploading.

Upload Monitoring#

The library provides the following functionality for monitoring uploading.

Monitoring the Upload Queue#

The following code example demonstrates getting a queue observable.

var disposable: Disposable? = nullval sortMode: FileTreeBrowser.Sort = FileTreeBrowser.Sort.NO_SORTING // Sort mode.// FileTreeBrowser.Sort:// * NO_SORTING - No sorting (default)// * NAME_ASC_DIR_FIRST - Sort by name ascending; Directories first.// * TYPE_ASC_DIR_FIRST - Sort by type ascending; Directories first.// * MODIFIED_DESC_DIR_FIRST - Sort by modification time descending; Directories first.// * SIZE_DESC_DIR_FIRST - Sort by size descending; Directories first.
// ...
// Returns Observable, which emits content of uploading queue.//// Returned observable emits every time when one of the following is changed:////   * New file enqueued for uploading//   * File cancelled uploading//   * File status changed//   * Progress changed//// Queue content provided as list of `FileItem`s.//// @param sort Sort mode.// @return Observable that emits uploading queue content. You have to subscribe on returned// observable in order to receive updates. You must call dispose() on disposable that returned// by subscribe() method, when it no longer needed.disposable = FileManager.uploader.getQueueObservable(sortMode)        .subscribe { // it: List<FileItem>!            // Update your recycler view adapter with received list.        }
// ...
// You must call dispose() on disposable that returned by subscribe() method,// when it is no longer needed, for example in your fragment’s onStop() or onPause().disposable?.dispose()

Links - FileTreeBrowser.Sort.

Single File Upload Monitoring#

The following code example demonstrates a way to monitor a file.

var disposable: Disposable? = null
val remoteFilePath: String = "/remote/file" // Remote file path.
// ...
// Returns Observable, which emits updates of file being uploaded.//// Returned observable emits every time when one of the following is changed:////   * File status changed//   * Progress changed//   * File cancelled uploading//// Important: Returned observable should be subscribed on a thread with Looper (i.e. main thread),// otherwise db listener won't work.//// @return Observable that emits updates of file being uploaded. You have to subscribe on returned// observable in order to receive updates. You must call dispose() on disposable that returned// by subscribe() method, when it no longer needed.disposable = FileManager.uploader.getItemObservable(remoteFilePath)        .subscribeBy(            onNext = { // it - FileItem                // Update your recycler view adapter with received list.            },            onError = { // it - Throwable                // Handle it            }        )
// ...
// You must call dispose() on disposable that returned by subscribe() method,// when it no longer needed, for example in your fragment’s onStop() or onPause().disposable?.dispose()

Upload Status#

The following code example demonstrates a file upload.

var disposable: Disposable? = null
// ...
// Subscribe to receive upload status updates.// `uploadStatus` observable emits UploaderEvent each time when uploading status changed.disposable = FileManager.uploader.uploadStatus.subscribe { // it: UploaderEvent    // To determine that all uploads finished (successfully or with error)    // and queue is empty, check the following condition:    if (it.filesLeft == 0 && (it.isDone || it.isCancelled || it.error != null)) {        // queue is empty    } else {        // queue is not empty    }}
// ...
// You must call dispose() on disposable that returned by subscribe() method,// when it no longer needed, for example in your fragment’s onStop() or onPause().disposable?.dispose()