How to Schedule Periodic Background Tasks in Android 13 and 14 Using WorkManager or Alternatives?

543 views Asked by At

I'm currently developing an Android app, and I need to schedule periodic background tasks to run every hour on devices running Android 13 and 14. I've tried using the WorkManager with PeriodicWorkRequest, and it works perfectly on Android versions below 13. However, it doesn't seem to work as expected on Android 13 and 14.

I've checked the documentation and haven't found any specific issues related to these Android versions. Additionally, I've heard that some mobile manufacturers may not allow certain background tasks. Can anyone provide guidance on how to schedule periodic background tasks for each hour on Android 13 and 14 using WorkManager or any other suitable approach, considering potential restrictions by mobile manufacturers? Your insights and solutions would be greatly appreciated!

My Manifest Permissions

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

My Notification Helper

object NotificationHandler {
    private const val CHANNEL_ID = "transactions_reminder_channel"

    fun createNotification(context: Context, title: String, content: String) {
        //  No back-stack when launched
        val intent = Intent(context, MainActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        }
        val pendingIntent = PendingIntent.getActivity(
            context, 0, intent,
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE
            else PendingIntent.FLAG_UPDATE_CURRENT
        )

        createNotificationChannel(
            context,
            title,
            content
        ) // This won't create a new channel everytime, safe to call

        val builder = NotificationCompat.Builder(context, CHANNEL_ID)
            .setSmallIcon(R.mipmap.ic_launcher_round)
            .setContentTitle(title)
            .setContentText(content)
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setContentIntent(pendingIntent) // For launching the MainActivity
            .setAutoCancel(true) // Remove notification when tapped
            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)

        with(NotificationManagerCompat.from(context)) {
            if (ActivityCompat.checkSelfPermission(
                    context,
                    Manifest.permission.POST_NOTIFICATIONS
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                return
            }
            notify(1, builder.build())
        }
    }

    /**
     * Required on Android O+
     */
    private fun createNotificationChannel(context: Context, title: String, content: String) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val importance = NotificationManager.IMPORTANCE_HIGH
            val channel = NotificationChannel(CHANNEL_ID, title, importance).apply {
                description = content
            }
            val notificationManager: NotificationManager =
                context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            notificationManager.createNotificationChannel(channel)
        }
    }
}

My Application Class

@HiltAndroidApp
class App : Application(), Configuration.Provider {
    @Inject
    lateinit var notificationWorkManagerFactory: NotificationWorkManagerFactory
    override fun getWorkManagerConfiguration(): Configuration {
        return Configuration.Builder()
            .setMinimumLoggingLevel(Log.DEBUG)
            .setWorkerFactory(notificationWorkManagerFactory)
            .build()
    }
}

class NotificationWorkManagerFactory @Inject constructor() : WorkerFactory() {
    override fun createWorker(
        appContext: Context,
        workerClassName: String,
        workerParameters: WorkerParameters
    ): ListenableWorker {
        return NotificationWorker(appContext, workerParameters)
    }
}

My Worker Class

class NotificationWorker @AssistedInject constructor(
    @Assisted var context: Context,
    @Assisted parameters: WorkerParameters,
) :
    CoroutineWorker(context, parameters) {
    override suspend fun doWork(): Result {
        return try {
            NotificationHandler.createNotification(context = context, title = "Hi from CoroutineWorker", "CoroutineWorker helps support multi-threaded coroutine usage in common code that works in Kotlin/Native and on JVM until kotlinx.")
            Result.success()
        } catch (e: Exception) {
            Log.d("TAG", "doWork: "+e.message)
            Result.failure()
        }
    }
}

And, This is my Activity

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val context = LocalContext.current
            val lifecycleOwner = LocalLifecycleOwner.current
            val workManagerState = remember { mutableStateOf("") }
            WorkerTheme {
                LaunchedEffect(key1 = Unit) {
                    val workManager = WorkManager.getInstance(this@MainActivity)
                    val request: PeriodicWorkRequest =
                        PeriodicWorkRequestBuilder<NotificationWorker>(
                            30,
                            TimeUnit.MINUTES
                        ).build()
                    workManager.enqueueUniquePeriodicWork(
                        "reminder_notification_work",
                        ExistingPeriodicWorkPolicy.UPDATE,
                        request
                    )
                    workManager.getWorkInfosForUniqueWorkLiveData("reminder_notification_work")
                        .observe(lifecycleOwner) { workInfo ->
                            workInfo.forEach {
                                workManagerState.value = it.state.toString()
                            }
                        }
                }
                Text(
                    text = "Hello ${workManagerState.value}!",
                    modifier = Modifier.fillMaxWidth()
                )
            }
        }
    }
}
1

There are 1 answers

5
Dimi On

What kind of issues you have with android 13/14 and PeriodicWorkRequest. I am also using it with android 13/14 and does not have any issue. My usecase is updating the widgets every 15mins. So far i did not regognized a big issue.

The time of execution of the work could diff because the system is deciding when to execute the work even you configured every 60 mins. It could take a bit longer until executing. Huge benefit of the WorkManager and PeriodicWorkRequest that they also outlive a reboot.

Could you post code snipped how you configure it? Are you targeting newest compile/target SDK?