Blog Infos
Author
Published
Topics
Author
Published
class FileDownloadWorker(
private val context:Context,
workerParameters: WorkerParameters
): CoroutineWorker(context, workerParameters) {
override suspend fun doWork(): Result {
}
}

Now define two constants objects one for input and output data and other for notifications.

object FileParams{
const val KEY_FILE_URL = "key_file_url"
const val KEY_FILE_TYPE = "key_file_type"
const val KEY_FILE_NAME = "key_file_name"
const val KEY_FILE_URI = "key_file_uri"
}
object NotificationConstants{
const val CHANNEL_NAME = "download_file_worker_demo_channel"
const val CHANNEL_DESCRIPTION = "download_file_worker_demo_description"
const val CHANNEL_ID = "download_file_worker_demo_channel_123456"
const val NOTIFICATION_ID = 1
}
private fun getSavedFileUri(
fileName:String,
fileType:String,
fileUrl:String,
context: Context): Uri?{
val mimeType = when(fileType){
"PDF" -> "application/pdf"
"PNG" -> "image/png"
"MP4" -> "video/mp4"
else -> ""
} // different types of files will have different mime type
if (mimeType.isEmpty()) return null
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, "Download/DownloaderDemo")
}
val resolver = context.contentResolver
val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
return if (uri!=null){
URL(fileUrl).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(fileUrl).openStream().use { input->
FileOutputStream(target).use { output ->
input.copyTo(output)
}
}
return target.toUri()
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
val name = NotificationConstants.CHANNEL_NAME
val description = NotificationConstants.CHANNEL_DESCRIPTION
val importance = NotificationManager.IMPORTANCE_HIGH
val channel = NotificationChannel(NotificationConstants.CHANNEL_ID,name,importance)
channel.description = description
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager?
notificationManager?.createNotificationChannel(channel)
}
val builder = NotificationCompat.Builder(context,NotificationConstants.CHANNEL_ID)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle("Downloading your file...")
.setOngoing(true)
.setProgress(0,0,true)
NotificationManagerCompat.from(context).notify(NotificationConstants.NOTIFICATION_ID,builder.build())

We only need to show notification while saving of file is in progress. After that we have to cancel it as well.
Now the doWork() method will something look like this.

override suspend fun doWork(): Result {
val fileUrl = inputData.getString(FileParams.KEY_FILE_URL) ?: ""
val fileName = inputData.getString(FileParams.KEY_FILE_NAME) ?: ""
val fileType = inputData.getString(FileParams.KEY_FILE_TYPE) ?: ""
Log.d("TAG", "doWork: $fileUrl | $fileName | $fileType")
if (fileName.isEmpty()
|| fileType.isEmpty()
|| fileUrl.isEmpty()
){
Result.failure()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
val name = NotificationConstants.CHANNEL_NAME
val description = NotificationConstants.CHANNEL_DESCRIPTION
val importance = NotificationManager.IMPORTANCE_HIGH
val channel = NotificationChannel(NotificationConstants.CHANNEL_ID,name,importance)
channel.description = description
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager?
notificationManager?.createNotificationChannel(channel)
}
val builder = NotificationCompat.Builder(context,NotificationConstants.CHANNEL_ID)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle("Downloading your file...")
.setOngoing(true)
.setProgress(0,0,true)
NotificationManagerCompat.from(context).notify(NotificationConstants.NOTIFICATION_ID,builder.build())
val uri = getSavedFileUri(
fileName = fileName,
fileType = fileType,
fileUrl = fileUrl,
context = context
)
NotificationManagerCompat.from(context).cancel(NotificationConstants.NOTIFICATION_ID)
return if (uri != null){
Result.success(workDataOf(FileParams.KEY_FILE_URI to uri.toString()))
}else{
Result.failure()
}
}

Step 3: Define File to be downloaded Model

data class File(
val id:String,
val name:String,
val type:String,
val url:String,
var downloadedUri:String?=null,
var isDownloading:Boolean = false,
)
view raw File.kt hosted with ❤ by GitHub
@Composable
fun ItemFile(
file: File,
startDownload:(File) -> Unit,
openFile:(File) -> Unit
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxWidth()
.background(color = Color.White)
.border(width = 2.dp, color = Color.Blue, shape = RoundedCornerShape(16.dp))
.clickable {
if (!file.isDownloading){
if (file.downloadedUri.isNullOrEmpty()){
startDownload(file)
}else{
openFile(file)
}
}
}
.padding(16.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier
.fillMaxWidth(0.8f)
) {
Text(
text = file.name,
style = Typography.body1,
color = Color.Black
)
Row {
val description = if (file.isDownloading){
"Downloading..."
}else{
if (file.downloadedUri.isNullOrEmpty()) "Tap to download the file" else "Tap to open file"
}
Text(
text = description,
style = Typography.body2,
color = Color.DarkGray
)
}
}
if (file.isDownloading){
CircularProgressIndicator(
color = Color.Blue,
modifier = Modifier
.size(32.dp)
.align(Alignment.CenterVertically)
)
}
}
}
}
view raw ItemFile.kt hosted with ❤ by GitHub

Job Offers

Job Offers


    Android Test Automation Engineer

    Komoot
    Remote
    • Full Time
    apply now

    Android Software Engineer (f/m/d)

    Paradox Cat GmbH
    Munich
    • Full Time
    apply now

    Senior Android Software Engineer (f/m/d)

    Paradox Cat GmbH
    Munich
    • Full Time
    apply now

OUR VIDEO RECOMMENDATION

, ,

From Scoped Storage to Photo Picker: Everything to know about Storage

Persistence is a core element of every mobile app. Android provides different APIs to access or expose files with different tradeoffs.
Watch Video

From Scoped Storage to Photo Picker: Everything to know about Storage

Yacine Rezgui
Android developer advocate
Google

From Scoped Storage to Photo Picker: Everything to know about Storage

Yacine Rezgui
Android developer ad ...
Google

From Scoped Storage to Photo Picker: Everything to know about Storage

Yacine Rezgui
Android developer advocat ...
Google

Jobs

<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"/>
class MainActivity : ComponentActivity() {
private lateinit var requestMultiplePermission: ActivityResultLauncher<Array<String>>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestMultiplePermission = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
){
var isGranted = false
it.forEach { s, b ->
isGranted = b
}
if (!isGranted){
Toast.makeText(this, "Permission Not Granted", Toast.LENGTH_SHORT).show()
}
}
setContent {
DownloadFileWorkManagerDemoTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
requestMultiplePermission.launch(
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
)
Home()
}
}
}
}
view raw MainActivity.kt hosted with ❤ by GitHub
private fun startDownloadingFile(
file: File,
success:(String) -> Unit,
failed:(String) -> Unit,
running:() -> Unit
) {
val data = Data.Builder()
data.apply {
putString(FileDownloadWorker.FileParams.KEY_FILE_NAME, file.name)
putString(FileDownloadWorker.FileParams.KEY_FILE_URL, file.url)
putString(FileDownloadWorker.FileParams.KEY_FILE_TYPE, file.type)
}
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresStorageNotLow(true)
.setRequiresBatteryNotLow(true)
.build()
val fileDownloadWorker = OneTimeWorkRequestBuilder<FileDownloadWorker>()
.setConstraints(constraints)
.setInputData(data.build())
.build()
workManager.enqueueUniqueWork(
"oneFileDownloadWork_${System.currentTimeMillis()}",
ExistingWorkPolicy.KEEP,
fileDownloadWorker
)
workManager.getWorkInfoByIdLiveData(fileDownloadWorker.id)
.observe(this){ info->
info?.let {
when (it.state) {
WorkInfo.State.SUCCEEDED -> {
success(it.outputData.getString(FileDownloadWorker.FileParams.KEY_FILE_URI) ?: "")
}
WorkInfo.State.FAILED -> {
failed("Downloading failed!")
}
WorkInfo.State.RUNNING -> {
running()
}
else -> {
failed("Something went wrong")
}
}
}
}
}
view raw MainActivity.kt hosted with ❤ by GitHub
@Composable
fun Home() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(32.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
val data = remember {
mutableStateOf(
File(
id = "10",
name = "Pdf File 10 MB",
type = "PDF",
url = "https://www.learningcontainer.com/wp-content/uploads/2019/09/sample-pdf-download-10-mb.pdf",
downloadedUri = null
)
)
}
ItemFile(
file = data.value,
startDownload = {
startDownloadingFile(
file = data.value,
success = {
data.value = data.value.copy().apply {
isDownloading = false
downloadedUri = it
}
},
failed = {
data.value = data.value.copy().apply {
isDownloading = false
downloadedUri = null
}
},
running = {
data.value = data.value.copy().apply {
isDownloading = true
}
}
)
},
openFile = {
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(it.downloadedUri?.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()
}
}
)
}
}
view raw MainActivity.kt hosted with ❤ by GitHub

That’s it for this example. See you in the next one.
You can connect with me on LinkedIn and Twitter.

This article was originally published on proandroiddev.com on March 04, 2022

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
In this article, we will learn about WorkManager best practices including performant component initialization…
READ MORE
blog
Uploading images to a remote server is an intensive operation. We would usually use…
READ MORE
blog
I paired Glance Widget with Work Manager API to create a feature for my…
READ MORE
blog
Notifications provide short, timely information about events in your app while it’s not in…
READ MORE

Leave a Reply

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

Fill out this field
Fill out this field
Please enter a valid email address.

Menu