Skip to main content

User-initiated Data Transfer Job

warning

User-initiated data transfer job is only available in API level 34 (Android 14) or higher.

One option we can use to do long data operation is by using user-initiated data transfer job.

Characteristics​

User-initiated data transfer jobs have these characteristics:

  1. They are started by the user.
  2. They require a notification.
  3. They start immediately.
  4. They may be able to run for an extended period of time as system condition allow.
  5. They can be run concurrently with other user-initiated data transfer jobs.
  6. They can be stopped by user via Task Manager or by the system.
  7. They can only be started when the conditions are met.
  8. You can define the constraints for the job to be run by the system.

Basic Usage​

First, we need to add RUN_USER_INITIATED_JOBS permission to our AndroidManifest.xml file. We also need to these 2 additional permissions:

  1. ACCESS_NETWORK_STATE
  2. POST_NOTIFICATIONS
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<!-- Add this permission: -->
<uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" />
<!-- We also need to add this permission to specify job network constraint: -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- We also need to add this permission to post notification: -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<application>
</application>

</manifest>

The next thing you need to do is create a subclass of JobService for the data transfer. Here's a simple JobService example that pretends to download data from Internet:

DummyDownloadJobService.kt
package com.hanmajid.androidnotebook

import android.Manifest
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.job.JobParameters
import android.app.job.JobService
import android.content.pm.PackageManager
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationManagerCompat

/**
* User-initiated data transfer job service for (dummy) downloading files.
*/
class DummyDownloadJobService : JobService() {
override fun onStartJob(params: JobParameters): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
// Create notification channel.
val name = "Download Data"
val importance = NotificationManager.IMPORTANCE_LOW
val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, name, importance)
val notificationManager = getSystemService(NotificationManager::class.java)
notificationManager.createNotificationChannel(channel)

// Post initial notification.
postNotification(params, 0.0)

// Create a new thread for the actual work.
// In this example, we only count from 0 to 10 while updating the notification text.
val handlerThread = HandlerThread("MyDownloadThread").apply {
start()
}
val handler = Handler(handlerThread.looper)
handler.post {
for (i in 0..9) {
// Update progress to notification.
postNotification(params, i / 10.0)
Thread.sleep(1000L)
}
// Update completion to notification.
postNotification(params, 1.0)

// Finish the job.
jobFinished(params, false)
}

// Returns true so that the service is keep running.
return true
} else {
// Returns false so that the service is stopped.
return false
}
}

@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
private fun postNotification(params: JobParameters, progress: Double) {
val contentText = if (progress < 1) {
"Progress: ${progress * 100}%"
} else {
"Completed!"
}
val notification = Notification.Builder(applicationContext, NOTIFICATION_CHANNEL_ID)
.setContentTitle("Downloading your file")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentText(contentText)
.build()
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
) {
NotificationManagerCompat.from(this)
.notify(
NOTIFICATION_ID,
notification,
)
}

// Set notification to job.
setNotification(
params,
NOTIFICATION_ID,
notification,
JOB_END_NOTIFICATION_POLICY_DETACH,
)
}

override fun onStopJob(params: JobParameters): Boolean {
// Returns true so that the job can be retried rescheduled based on the retry criteria.
return true
}

companion object {
// Notification channel ID for downloading data.
const val NOTIFICATION_CHANNEL_ID = "download-data-channel"

// Notification ID for downloading data.
const val NOTIFICATION_ID = 123
}
}

Next, we need to register this class to our AndroidManifest.xml file:

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<application>

<service
android:name=".DummyDownloadJobService"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" />
</application>

</manifest>

Lastly, you need to schedule your job with JobScheduler. For user-initiated data transfer jobs, its necessary to use setUserInitiated() method:

import android.app.job.JobInfo
import android.app.job.JobScheduler
import android.content.ComponentName
import android.content.Context
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import androidx.annotation.RequiresApi

// Define network constraint for the job.
val networkRequestBuilder = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
.build()

// Define the job and its constraints.
val jobInfo = JobInfo.Builder(1, ComponentName(context, DummyDownloadJobService::class.java))
.setUserInitiated(true)
.setRequiredNetwork(networkRequestBuilder)
.build()

// Schedule the job.
val jobScheduler = context.getSystemService(JobScheduler::class.java)
jobScheduler.schedule(jobInfo)

Here's what happen when we try schedule the data transfer job:

References​