In this tutorial, we are going to download a file using work manager and save it in your location storage in kotlin. WorkManager in Android is one of the parts of Architecture Components and Android Jetpack that runs the postponed background task when the task’s restrictions are fulfilled. WorkManager controls the background task that needs to run when various restrictions are gathered, irrespective of whether the application process is viable or not.
When to Use Android WorkManager?
Although there are different ways for background task execution like ForegroundService,JobScheduler,AlarmManager,and Firebase JobDispatcher, here we will prefer to use Android WorkManager as it can easily set up a task for run & forward it to the system based on specified conditions.
There are a few use cases where it is suitable to use WorkManager, such as
- Upload files to the server
- Syncing data to or from the server
- Sending logs to the server
- Executing the valuable process
Already, I have explained the work manager in detail in another tutorial.
Getting started with WorkManager [Example]
How to Download Files Using Android WorkManager Example
Let’s get into the example, of downloading files using workmanager.
Add workManager dependency to the project
Add dependency in application module Gradle file (build.gradle)
implementation "androidx.work:work-runtime-ktx:2.7.1"
Add permissions to your Manifest File
Set permission inside the Manifest.
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Ask permission from the user
Need run time permission for the storage access. First, check whether the storage permission is granted or not.
private fun checkPermission(permission: String): Boolean {
return ContextCompat.checkSelfPermission(
this,
permission
) == PackageManager.PERMISSION_GRANTED
}
If permission is not granted, then ask for storage permission.
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE),
201
)
Check more about the Runtime permission on Android,
Define a File Download Worker
Create a work manager class that extends with Worker and overrides the doWork()
method.
class FileDownloadWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
/**
* We have performed download task here
*/
return Result.success()
}
}
Before starting downloading a file, For displaying notifications, we need to add NotificationChannels for Android Oreo onwards.
private fun displayNotification() {
val channel = NotificationChannel(
CHANNEL_NAME,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT
)
channel.enableVibration(false)
notificationManager.createNotificationChannel(channel)
val notificationBuilder =
NotificationCompat.Builder(applicationContext, CHANNEL_NAME)
notificationBuilder
.setContentTitle(CHANNEL_DESC)
.setSmallIcon(R.drawable.ic_launcher_background)
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
}
After displaying the notification, next need to download the file and save it in the downloads location. To download a file, we need to follow the two different approaches for the pre-Q Android Version and the Post-Q Android version.
Also, you need to get the mime type of the file from the file type.
val mimeType = when(workerParameters.inputData.getString(KEY_FILE_TYPE)){
"PDF" -> "application/pdf"
"PNG" -> "image/png"
"MP4" -> "video/mp4"
else -> ""
}
we can get the file URL and filename from the workmanager inputdata.
val filename = workerParameters.inputData.getString(KEY_FILE_NAME)
val url = workerParameters.inputData.getString(KEY_FILE_URL)
Once the file URL, filename, and mime type id are ready, we can download the file now.
private fun downloadFileFromUri(url: String,mimeType: String, filename: String?): Uri? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
}
val resolver = context.contentResolver
val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
return if (uri != null) {
URL(url).openStream().use { input ->
resolver.openOutputStream(uri).use { output ->
input.copyTo(output!!, DEFAULT_BUFFER_SIZE)
}
}
uri
} else {
null
}
} else {
val target = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
filename
)
URL(url).openStream().use { input ->
FileOutputStream(target).use { output ->
input.copyTo(output)
}
}
return target.toUri()
}
}
after the file download is completed, we need to cancel the notification using the channel ID. Also, need to return the resulting success for the doWork()
function. If any happened during the file download operation it will return the failure to doWork()
function.
override fun doWork(): Result {
displayNotification()
val mimeType = when(workerParameters.inputData.getString(KEY_FILE_TYPE)){
"PDF" -> "application/pdf"
"PNG" -> "image/png"
"MP4" -> "video/mp4"
else -> ""
}
val filename = workerParameters.inputData.getString(KEY_FILE_NAME)
val url = workerParameters.inputData.getString(KEY_FILE_URL)
url?.let {
return try {
val uri = downloadFileFromUri(url,mimeType,filename )
uri?.let {
}
notificationManager.cancel(NOTIFICATION_ID)
Result.success(workDataOf(KEY_FILE_URI to uri.toString()))
} catch (e: Exception) {
e.printStackTrace()
Result.failure()
}
}
return Result.failure()
}
Set Constraints & start(enqueue) workmanager task
We’ll be creating a one-time request. As the name suggests, it’ll fire once and stop. If you want to schedule periodic tasks, consider using PreiodicWorkRequest. Use the builder pattern from OneTimeRequest. Pass FileDownloadWorker::class.java in the builder constructor.
Along with onTimeRequest
, we can pass the input data for the DownloadWorker using setInputData()
. Once the constraint setup is completed, we can start the worker using workManager.enqueue()
.
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresStorageNotLow(true)
.setRequiresBatteryNotLow(true)
.build()
val data = Data.Builder()
data.apply {
putString(KEY_FILE_NAME, "sample.pdf")
putString(KEY_FILE_URL, "http://www.africau.edu/images/default/sample.pdf")
putString(KEY_FILE_TYPE, "PDF")
}
val oneTimeWorkRequest = OneTimeWorkRequest.Builder(FileDownloadWorker::class.java)
.setConstraints(constraints)
.setInputData(data.build())
.build()
workManager.enqueue(oneTimeWorkRequest)
After the work manager started the work, we can get the status of the work using the workManager.getWorkInfoByIdLiveData(oneTimeWorkRequest.id)
.
workManager.getWorkInfoByIdLiveData(oneTimeWorkRequest.id)
.observe(this) { info ->
info?.let {
when (it.state) {
WorkInfo.State.SUCCEEDED -> {
btnOpenFile.text = "Download completed"
}
WorkInfo.State.FAILED -> {
btnOpenFile.text = "Download in failed"
}
WorkInfo.State.RUNNING -> {
btnOpenFile.text = "Download in progress.."
}
else -> {
}
}
}
}
After the work is completed, we can open the file using intent.
val uri = it.outputData.getString(KEY_FILE_URI)
uri?.let {
btnOpenFile.text = "Open File"
btnOpenFile.visibility = View.VISIBLE
btnOpenFile.setOnClickListener {
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(uri.toUri(), "application/pdf")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
try {
startActivity(intent)
} catch (e: ActivityNotFoundException) {
Toast.makeText(this@MainActivity,
"Can't open Pdf",
Toast.LENGTH_SHORT).show()
}
}
}
full MainActivity.kt
class MainActivity : AppCompatActivity() {
lateinit var workManager: WorkManager
lateinit var btnOpenFile: Button
lateinit var btnStartDownload: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
workManager = WorkManager.getInstance(this)
btnOpenFile = findViewById(R.id.btnOpenFile)
btnStartDownload = findViewById(R.id.btnStartDownload)
btnStartDownload.setOnClickListener {
startDownloadingFile()
btnOpenFile.visibility = View.GONE
}
if (!checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE) && !checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
ActivityCompat.requestPermissions(
this,
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
),
201
)
}
}
private fun checkPermission(permission: String): Boolean {
return ContextCompat.checkSelfPermission(
this,
permission
) == PackageManager.PERMISSION_GRANTED
}
private fun startDownloadingFile(
) {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresStorageNotLow(true)
.setRequiresBatteryNotLow(true)
.build()
val data = Data.Builder()
data.apply {
putString(KEY_FILE_NAME, "sample.pdf")
putString(KEY_FILE_URL, "http://www.africau.edu/images/default/sample.pdf")
putString(KEY_FILE_TYPE, "PDF")
}
val oneTimeWorkRequest = OneTimeWorkRequest.Builder(FileDownloadWorker::class.java)
.setConstraints(constraints)
.setInputData(data.build())
.build()
workManager.enqueue(oneTimeWorkRequest)
workManager.getWorkInfoByIdLiveData(oneTimeWorkRequest.id)
.observe(this) { info ->
info?.let {
when (it.state) {
WorkInfo.State.SUCCEEDED -> {
val uri = it.outputData.getString(KEY_FILE_URI)
uri?.let {
btnOpenFile.text = "Open File"
btnOpenFile.visibility = View.VISIBLE
btnOpenFile.setOnClickListener {
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(uri.toUri(), "application/pdf")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
try {
startActivity(intent)
} catch (e: ActivityNotFoundException) {
Toast.makeText(
this@MainActivity,
"Can't open Pdf",
Toast.LENGTH_SHORT
).show()
}
}
}
}
WorkInfo.State.FAILED -> {
btnOpenFile.text = "Download in failed"
}
WorkInfo.State.RUNNING -> {
btnOpenFile.text = "Download in progress.."
}
else -> {
}
}
}
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 201) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Toast.makeText(this, "Permission granted", Toast.LENGTH_SHORT).show()
//openCamera()
} else {
Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show()
}
}
}
}
That’s all. Thanks for reading. You can download the example from GitHub.
Leave a Reply