Skip to content

Configuration

Alex Gotev edited this page Aug 23, 2021 · 40 revisions

Index

Foreword

It's advised to do all the following configurations on App startup, before everything else gets started. For example, you can do it in the Application subclass, like shown in the example apps.

All the code examples are in Kotlin, but everything is 100% interoperable with Java and so you can use Android Upload Service without any concern even if your entire codebase is 100% Java.

Notifications

When you create a new upload request and you start it, Upload Service asks UploadServiceConfig.notificationConfigFactory to create a notification for it. You can set your own notificationConfigFactory and also override the default per single upload using request.setNotificationConfig depending on what you need.

Both UploadServiceConfig.notificationConfigFactory and request.setNotificationConfig have the same interface and expect you to return an UploadNotificationConfig:

// Global default configuration for all uploads
// It's advised you do this one time and right after you have 
// initialized the library in your Application
UploadServiceConfig.notificationConfigFactory = { context, uploadId ->
    // your implementation
}

// Custom configuration for a single upload
// You can do this in any place you find convenient
MultipartUploadRequest(context, "https://my.secure.server")
    .setNotificationConfig { context, uploadId ->
        // your implementation
    }

UploadNotificationConfig

Minimal Upload Notification Config looks like this:

UploadNotificationConfig(
    notificationChannelId = UploadServiceConfig.defaultNotificationChannel!!,
    isRingToneEnabled = true,
    progress = UploadNotificationStatusConfig(
        title = "progress",
        message = "some progress message"
    ),
    success = UploadNotificationStatusConfig(
        title = "success",
        message = "some success message"
    ),
    error = UploadNotificationStatusConfig(
        title = "error",
        message = "some error message"
    ),
    cancelled = UploadNotificationStatusConfig(
        title = "cancelled",
        message = "some cancelled message"
    )
)

It specifies:

  • the notification channel on which to operate. You can safely use UploadServiceConfig.defaultNotificationChannel!! which is guaranteed non-null after you performed Upload Service initialization. You can also use another custom notification channel, but remember to create it first and configure it, like you've done with the default one!
  • isRingToneEnabled. This affects the behaviour on only Android < API 26 and sets whether you want the notification sound. On Android API >= 26 this setting is done when you create the notification channel.
  • progress. Configuration for the notification when the upload is in progress.
  • success. Configuration for the notification when the upload has been completed successfully.
  • error. Configuration for the notification when the upload has failed.
  • cancelled. Configuration for the notification when the upload has been cancelled by the user.

UploadServiceConfig.namespace will also be automatically used as the group ID for your notifications.

Take also a look to the default which is richer, but keep reading below to understand it fully.

UploadNotificationStatusConfig

Minimal:

UploadNotificationStatusConfig(
    title = "progress",
    message = "message"
)

Take a look to the docs in the code to discover all the available options.

You can use your own localised strings and drawable resources, but keep in mind to only use the context which is given to you in the lambda where you set the notification configuration, to prevent memory leaks and unwanted crashes!

Placeholders

You can make your title and message richer by using Placeholders which gets replaced at runtime by Upload Service with information about the upload. You can use them from the code:

UploadNotificationStatusConfig(
    title = "progress: ${Placeholder.Progress}",
    message = "message"
)

or add the equivalent literal in your strings.xml

<string name="progress">progress: [[PROGRESS]]</string>

and use it:

UploadNotificationStatusConfig(
    title = context.getString(R.string.progress),
    message = "message"
)

You can safely use many placeholders in a single string 😉

Placeholders processor

By default you can include the following placeholders in your notification title and messages, but if you need more customization, you can add your own placeholders by extending DefaultPlaceholdersProcessor or by implementing the PlaceholdersProcessor interface. Extend DefaultPlaceholdersProcessor if you still want to use default placeholders + your own or implement PlaceholdersProcessor if you want to use only your custom set of placeholders.

To set your custom placeholders processor:

UploadServiceConfig.placeholdersProcessor = YourCustomImplementation()

Do you think your implementation may be useful to other developers? Propose a PR!

Actions

For each UploadNotificationStatusConfig you can specify one or more actions (Google advises a max of 3):

UploadNotificationAction(
    icon = android.R.drawable.ic_menu_close_clear_cancel,
    title = "Cancel",
    intent = context.getCancelUploadIntent(uploadId)
)

The action has an icon (applied only on Android older than 7), a title (you can use localized strings) and a PendingIntent to be performed when the user taps on the action. The PendingIntent can be whatever you need. There are no limitations. To ease you adding an upload cancel option which aborts the upload, you can use context.getCancelUploadIntent(uploadId). This is also the default set in the bundled UploadServiceConfig.notificationConfigFactory.

Notification Handler

By default, Android Upload Service creates one notification for each upload request you make, honoring all the configurations described in the previous paragraphs. The default behaviour may not be what you had in mind for your app, for that reason you can implement your own Notification Handler.

To make things easier for your custom implementation:

  • Look at NotificationHandler for a handler which creates one notification for each task.
  • Look at AbstractSingleNotificationHandler and ExampleSingleNotificationHandler for a handler which manages a single notification for all the tasks. Bear in mind AbstractSingleNotificationHandler gives you only the primitives on purpose, to allow maximum personalization and flexibility, but there's more work to do to achieve your desired result if compared to NotificationHandler. ExampleSingleNotificationHandler is a proof of concept to demonstrate one possible and minimal implementation and it's not meant to be production grade. There are many ways to display progress for multiple uploads in a single notification. Choosing one predefined and fully out of the box path would have limited other possible implementations. In updateNotification method you will get a Map<String, TaskData> where the key is the uploadID and TaskData the last known information about that upload task, including the corresponding UploadNotificationStatusConfig. It's entirely up to you to decide which settings to honor and which to ignore. Remember to call removeTask(uploadID) for the either failed or succeeded tasks you don't want to display anymore to free memory. No automatic garbage collection has been added to allow maximum flexibility.

To set your custom Notification Handler:

UploadServiceConfig.notificationHandlerFactory = { service ->
  // instantiate your implementation
}

Notification progress interval

Interval between progress notifications in milliseconds. If the upload tasks report more frequently than this value, upload service will automatically apply throttling. Default is 3 updates per second. This is done to not pollute the main thread with too many notification updates, which may cause the device to lag.

You can change this value by setting:

UploadServiceConfig.uploadProgressNotificationIntervalMillis = 1000 / 3

HTTP Stack

By default, Android Upload Service uses system's HttpURLConnection network stack, referred to as HurlStack. It supports OkHttp and has interfaces which allows to implement a custom HTTP Stack.

Default HurlStack

You don't have to do anything. This is the default configuration:

  • userAgent: Android Upload Service User Agent
  • followRedirects: true
  • useCaches: false
  • connectTimeoutMillis: 15000
  • readTimeoutMillis: 30000

HurlStack with custom configuration

Set it into your Application subclass, right after UploadServiceConfig.initialize. Example:

UploadServiceConfig.httpStack = HurlStack(
    userAgent = "MyCustomUserAgent",
    followRedirects = true,
    useCaches = false,
    connectTimeoutMillis = 20000,
    readTimeoutMillis = 60000
)

OkHttpStack

This is the recommended configuration.

starting from Android 4.4 KitKat, OkHttp is used internally to provide HttpURLConnection implementation, as reported here. The version used changes across different Android releases, so you may experience different behaviours and encounter some known bugs on older releases. To avoid having to deal with that, I suggest you to switch to the OkHttp stack implementation, to be sure to have a consistent behaviour across all Android versions and to have the most updated version of OkHttp as well.

Add this to your build.gradle:

implementation "net.gotev:uploadservice-okhttp:$uploadServiceVersion"

Then sync the project and set the following into your Application subclass, right after UploadServiceConfig.initialize:

UploadServiceConfig.httpStack = OkHttpStack()

This will create a new OkHttpClient with the same default settings as HurlStack. If you're already using OkHttp in your project, you can pass your own client instance to Upload Service like this:

UploadServiceConfig.httpStack = OkHttpStack(yourOkHttpClient)

Custom HTTP Stack

Not finding the HTTP stack you want to use? No problem, you can use your own network stack, too. You have to implement the following interfaces and base classes:

  • HttpStack: Connection factory which creates new requests
  • HttpRequest: HTTP request implementation
  • BodyWriter: HttpRequest delegate which handles writing the request body payload

ServerResponse is the Parcelable data class which holds a response got from the server.

See HurlStack and OkHttp stack implementations as a guideline.

Cookies

Use Android Cookie Store which provides a persistent cookie storage that can be used in conjunction with Android Upload Service, for both HttpURLConnection and OkHttp stacks.

Retry policy

Retry mechanism is triggered when there's a communication failure to/from the server due to connectivity problems (e.g. timeout, broken pipe, server unreachable, no network connection on the device)

If your server sends a response (either success or failure), the retry mechanism will not be triggered. You will receive the response in onSuccess or onError (check the Monitoring Wiki page) depending on the status sent by the server. In case of a failure (4xx or 5xx) received from the server, it means something was wrong on your server or the way you made the request, so to safeguard both the device and the server against an unwanted flow of requests, no automatic retries are made.

By default, when an upload fails, Upload Service will try again for a max of three times before signaling an error, applying a backoff multiplier of 2 seconds between each retry. You can customize this setting with:

UploadServiceConfig.retryPolicy = RetryPolicyConfig(
    initialWaitTimeSeconds = 1,
    maxWaitTimeSeconds = 100,
    multiplier = 2,
    defaultMaxRetries = 3
)

According to what you need. It's recommended to set this configuration where you initialize the library. Refer to RetryPolicyConfig for details about each parameter.

When you use the retry policy, upload service handles the retries transparently for you, throwing an error only once after all the attempts have failed. You can see this by checking the library debug logs. Your error handler will be called only once, after all the attempts have failed, giving you the last error which occurred.

If you don't want any retry policy, set this:

UploadServiceConfig.retryPolicy = RetryPolicyConfig(
    initialWaitTimeSeconds = 1,
    maxWaitTimeSeconds = 1,
    multiplier = 1,
    defaultMaxRetries = 0
)

Idle timeout

To preserve battery as much as possible, when the upload service finishes all the upload tasks, it immediately stops foreground execution, and remains sitting in the background waiting for new tasks to come, until the idle timeout is reached. By default, the service will auto shutdown after 10 seconds of inactivity, but you can tune that value with:

UploadServiceConfig.idleTimeoutSeconds = 60

Upload buffer size

Buffer size in bytes used for data transfer by the upload tasks. By default it's 4096 bytes. You can set it with:

UploadServiceConfig.bufferSizeBytes = 4096

Number of parallel uploads

By default, UploadService uses its own thread pool, with max threads equal to the number of processors on your device. Every upload takes a thread. So, for example, if you have a quad-core device, the number of maximum parallel uploads will be 4.

You can customize the thread pool initialization or also assign an already existing thread pool. Example:

UploadServiceConfig.threadPool = ThreadPoolExecutor(
    Runtime.getRuntime().availableProcessors(), // Initial pool size
    Runtime.getRuntime().availableProcessors(), // Max pool size
    5.toLong(), // Keep Alive Time
    TimeUnit.SECONDS,
    LinkedBlockingQueue()
)

So for example if you want to serialize upload tasks (only one upload at a time):

UploadServiceConfig.threadPool = ThreadPoolExecutor(
    1, // Initial pool size
    1, // Max pool size
    5.toLong(), // Keep Alive Time
    TimeUnit.SECONDS,
    LinkedBlockingQueue()
)

Logging

If you followed the getting started guide, the library logging will be enabled for debug builds and disabled for production builds.

you can also explicitly set a log level:

UploadServiceLogger.setLogLevel(UploadServiceLogger.LogLevel.Debug)

or development mode:

UploadServiceLogger.setDevelopmentMode(devModeOn = true)

In development mode, if you pass true, the library will log at debug level, otherwise the logging will be disabled.

By default, the logger will write to your LogCat, but you can also set your own delegate:

UploadServiceLogger.setDelegate(object : UploadServiceLogger.Delegate {
    override fun error(
        component: String,
        uploadId: String,
        message: String,
        exception: Throwable?
    ) {
        TODO("Implement your own logic")
    }

    override fun debug(component: String, uploadId: String, message: String) {
        TODO("Implement your own logic")
    }

    override fun info(component: String, uploadId: String, message: String) {
        TODO("Implement your own logic")
    }
})

So, for example, if you are logging using Timber or other library, you can send log outputs where you need.

Scheme Handlers (use your custom scheme other than file and content://)

A scheme handler allows you to implement custom scheme management and to use it when specifying the paths of the files to upload. For example FileSchemeHandler allows you to use absolute paths which refers to your device file system:

BinaryUploadRequest(context, serverUrl = "https://my.server.com")
    .setMethod("POST")
    .setFileToUpload("/path/to/your/file")
    .startUpload()

and ContentResolverSchemeHandler allows you to use content:// URIs such as:

BinaryUploadRequest(context, serverUrl = "https://my.server.com")
    .setMethod("POST")
    .setFileToUpload("content://myprovider/12343532")
    .startUpload()

Scheme Handlers work with all the supported upload request types in the same way.

To prevent issues and unwanted behaviors, it's not possible to replace existing FileSchemeHandler and ContentResolverSchemeHandler implementations, but you can add your own.

For example, let's say you want to implement the scheme foobar://. Create your class FoobarSchemeHandler which implements the methods of the SchemeHandler interface and then register your implementation:

UploadServiceConfig.addSchemeHandler("foobar://", FoobarSchemeHandler())

You can use your scheme when specifying file paths in your uploads:

BinaryUploadRequest(context, serverUrl = "https://my.server.com")
    .setMethod("POST")
    .setFileToUpload("foobar://mytopapp/myfile")
    .startUpload()