WorkManager

Uso de WorkManager

What WorkManager is and when to use WorkManager

How to use the WorkManager API to schedule Work

WorkManager and Kotlin

WorkManager Periodicity

Android WorkManager Tutorial: Getting Started

 

Crear un proyecto llamado WorkManagerDownload

dar permisos en el manifiesto

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

añadir view binding en el build.gradle del módulo

buildFeatures {
    viewBinding = true
}

usar la versión 17 de Java

compileOptions {
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
    jvmTarget = "17"
}

añadir la dependencia de WorkManager y corutinas

// WorkManager dependency
// implementation("androidx.work:work-runtime-ktx:2.10.0")
implementation(libs.androidx.work.runtime.ktx)

// Coroutines dependency
// implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1") 
implementation(libs.kotlinx.coroutines.core)

crear la clase DownloadWorker con la tarea a realizar

class DownloadWorker(appContext: Context, params: WorkerParameters) :  CoroutineWorker(appContext, params) {

    companion object {
        private const val TAG = "DownloadWorker"
        const val KEY_WEB_URL = "web_url"
        const val KEY_FILE_NAME = "file_name"
        const val KEY_FILE_PATH = "file_path"
        const val ERROR_MESSAGE = "error_message"
    }

    override suspend fun doWork(): Result {
        val webUrl = inputData.getString(KEY_WEB_URL) ?: return Result.failure()
        val fileName = inputData.getString(KEY_FILE_NAME) ?: return Result.failure()

        return try {
            Thread.sleep(5000)
            val route = withContext(Dispatchers.IO) {
                downloadFile(webUrl, fileName)
            }
            val outputData = workDataOf(KEY_FILE_PATH to route)
            Result.success(outputData)
        } catch (e: Exception) {
            Log.e(TAG, "Error downloading file: ${e.message}", e)
            val outputData = workDataOf(ERROR_MESSAGE to "Download failed: ${e.message}")
            Result.failure(outputData)
        }
    }

    private fun downloadFile(url: String, fileName: String): String {

        val url = URL(url)
        val connection = url.openConnection()
        connection.connect()

        val inputStream = BufferedInputStream(connection.getInputStream())
        val file = File(applicationContext.getExternalFilesDir(null), fileName) // Or use internal storage: File(applicationContext.filesDir, fileName)
        val outputStream = FileOutputStream(file)

        inputStream.copyTo(outputStream)

        outputStream.close()
        inputStream.close()

        return file.absolutePath
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Uso de WorkManager"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.096" />

    <Button
        android:id="@+id/botonIniciar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="60dp"
        android:text="Iniciar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.535"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <Button
        android:id="@+id/botonParar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="Parar"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.534"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/botonIniciar"
        app:layout_constraintVertical_bias="0.0" />

    <TextView
        android:id="@+id/salida"
        android:layout_width="260dp"
        android:layout_height="120dp"
        android:layout_marginTop="44dp"
        android:text="resultado de la descarga"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.582"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/botonParar"
        app:layout_constraintVertical_bias="0.0" />

</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity

class MainActivity : AppCompatActivity(), View.OnClickListener {

    lateinit var binding: ActivityMainBinding
    lateinit var downloadWorkRequest: OneTimeWorkRequest
    // lateinit var downloadWorkRequest: PeriodicWorkRequest
    var start: Boolean = false

    companion object {
        const val WEB = "https://dam.org.es/ficheros/frases.html"
        const val FILE_NAME = "frases.html"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        /*
        enableEdgeToEdge()
        setContentView(R.layout.activity_main)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
        */
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view: View = binding.root
        setContentView(view)

        binding.botonIniciar.setOnClickListener(this)
        binding.botonParar.setOnClickListener(this)

    }

    override fun onClick(v: View) {

        binding.salida.text = ""

        if (v == binding.botonIniciar) {
            startDownload(WEB, FILE_NAME)
            start = true
            binding.botonIniciar.isEnabled = false
        }
        if (v == binding.botonParar)
            if (start) {
                mostrarMensaje(downloadWorkRequest.id.toString() + " parado")
                Log.i("Parar", downloadWorkRequest.id.toString() + " parado")
                WorkManager.getInstance(this).cancelWorkById(downloadWorkRequest.id)
                start = false
                binding.botonIniciar.isEnabled = true
            }
    }

    private fun mostrarMensaje(mensaje: String) {
        Toast.makeText(this, mensaje, Toast.LENGTH_SHORT).show()
    }

    private fun startDownload(webUrl: String, fileName: String) {

        val constraints = Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build()

        val inputWorkData = workDataOf(
            DownloadWorker.KEY_WEB_URL to webUrl,
            DownloadWorker.KEY_FILE_NAME to fileName
        )

        downloadWorkRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
            .setConstraints(constraints)
            .setInputData(inputWorkData)
            .build()

        // downloadWorkRequest = PeriodicWorkRequestBuilder<DownloadWorker>(1, TimeUnit.HOURS).build()

        WorkManager.getInstance(this).enqueue(downloadWorkRequest)

        // You can observe the work's progress and output if needed:
        WorkManager.getInstance(this).getWorkInfoByIdLiveData(downloadWorkRequest.id)
            .observe(this) {
                workInfo ->
                when (workInfo?.state) {
                    WorkInfo.State.SUCCEEDED -> {
                        val filePath = workInfo.outputData.getString(DownloadWorker.KEY_FILE_PATH)
                        // Do something with the downloaded file path
                        Log.d("Download", "File downloaded to: $filePath")
                        mostrarMensaje(filePath.toString())
                        binding.salida.text = filePath.toString()
                        binding.botonIniciar.isEnabled = true
                    }

                    WorkInfo.State.FAILED -> {
                        // Handle failure
                        val errorMessage = workInfo.outputData.getString(DownloadWorker.ERROR_MESSAGE)
                        // Do something with the error message
                        Log.e("Download", "Download failed: $errorMessage")
                        mostrarMensaje(errorMessage.toString())
                        binding.salida.text = errorMessage.toString()
                        binding.botonIniciar.isEnabled = true
                    }
                    // ... other states (RUNNING, ENQUEUED, etc.)
                    WorkInfo.State.ENQUEUED ->
                        mostrarMensaje("Enqueued")
                    WorkInfo.State.RUNNING ->
                        mostrarMensaje("Running")
                    WorkInfo.State.BLOCKED ->
                        mostrarMensaje("Blocked")
                    WorkInfo.State.CANCELLED ->
                        mostrarMensaje("Cancelled")
                    else ->
                        mostrarMensaje("Otro estado")
                }
            }
    }

    public override fun onDestroy() {
        super.onDestroy()
        //cancelar los trabajos con WorkManager
        // if (start)
            // WorkManager.getInstance().cancelAllWork()
            WorkManager.getInstance(this).cancelAllWork()
    }
}

 

Más información:

Codelab: Trabajo en segundo plano con WorkManager

Workout your tasks with WorkManager

 

Deja una respuesta