Download File With Android Work Manager [Example]

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.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *


Latest Posts