How to start a startForegroundService using WorkManager - android

Hi due to issues with startForegroundService not able to run in the background on certain android version. I am trying to startForegroundService using a WorkManager but unable to get it to work. this is what I tried so far.
Forground service that needs to start
ContextCompat.startForegroundService(context, CarInfoProcessingService.createIntent(context = applicationContext,
pushMsgData = message.data))
class that starts the foreground service
class BackupWorker(private val context: Context, workerParams: WorkerParameters,private val message: RemoteMessage) :
Worker(context, workerParams) {
override fun doWork(): Result {
//call methods to perform background task
ContextCompat.startForegroundService(context, CarInfoProcessingService.createIntent(context = applicationContext,
pushMsgData = message.data))
return Result.success()
}
companion object {
private const val TAG = "BackupWorker"
}
}
how I am starting the workManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val request =
OneTimeWorkRequest.Builder(BackupWorker::class.java).addTag("BACKUP_WORKER_TAG")
.build()
WorkManager.getInstance(context).enqueue(request)
}
Issues I have
The above code does not trigger CarInfoProcessingService.
Another question I had was for class BackupWorker I have a parameter message but I am never passing that any where,
Thank you for your help in advance
R

Related

Check for network status update while application killed

My scenario is the following:
I'm working on a chat application and I would like to implement some type synchronization service that starts itself when device recovers network connection. Anytime device has network connection again, unsent messages are going to be automatically sent. With independence of application state (foregorund, background or killed).
Options tried:
1. Broadcast Receiver with android.net.conn.CONNECTIVITY_CHANGE
This scenario only works when the application is active (Foreground or Backround) but stops working when app is killed.
2. Foreground service
A notification is going to be shown all the time which is not ideal. Also I want to avoid draining users' battery.
3. AndroidX.Work.Worker
PeriodicWorkRequest networkCheckingPeriodicWork = PeriodicWorkRequest.Builder.
From<ConnectivityChangeWroker>(repeatInterval:30, repeatIntervalTimeUnit: Java.Util.Concurrent.TimeUnit.Minutes, flexInterval:25, flexIntervalTimeUnit:Java.Util.Concurrent.TimeUnit.Minutes)
.SetConstraints(new Constraints.Builder().SetRequiredNetworkType(AndroidX.Work.NetworkType.Connected)
.SetRequiredNetworkType(AndroidX.Work.NetworkType.Unmetered).Build()).Build();
WorkManager.Instance.EnqueueUniquePeriodicWork("", ExistingPeriodicWorkPolicy.Replace, networkCheckingPeriodicWork);
public class ConnectivityChangeWroker : AndroidX.Work.Worker
{
public ConnectivityChangeWroker(Context context, WorkerParameters workerParameters) : base(context, workerParameters)
{
}
public override Result DoWork()
{
try
{
//Start synch service
return Result.InvokeSuccess();
}
catch (Exception)
{
return Result.InvokeFailure();
}
}
}
But in this case, I'm not achieving the desired behaviour. For my undestanding, I just set a periodic work that checks for network connection, and if there is one, runs DoWork() method.
-- EDIT --
4.JobService
Java.Lang.Class javaClass = Java.Lang.Class.FromType(typeof(ConnectivityChangeJob));
ComponentName component = new ComponentName(Application.Context, javaClass);
JobInfo jobInfo = new JobInfo.Builder(1, component)
.SetRequiredNetworkType(Android.App.Job.NetworkType.Any)
.SetOverrideDeadline(5000)
.SetPersisted(true)
.Build();
JobScheduler jobScheduler = (JobScheduler)GetSystemService(JobSchedulerService);
jobScheduler.Schedule(jobInfo);
[Service(Name = "Extintores.ConnectivityChangeJob", Permission = "android.permission.BIND_JOB_SERVICE")]
public class ConnectivityChangeJob : JobService
{
private Intent startServiceIntent;
public ConnectivityChangeJob()
{
}
public override bool OnStartJob(JobParameters jobParams)
{
//Start synchService
return true;
}
public override bool OnStopJob(JobParameters jobParams)
{
return true; //Reschedule the job
}
}
But in this case, OnStartJob is only fired the first time the applicatio is opened and, apparently, never again.
Is there any way I can achieve what I'm aming for?
-- EDIT --
I want to achieve the same bahaviour as applications as WhatsApp. When it detects network connection again, automatically all unsent messages are going to be send.
I guess the AndroidX.Work.Worker is the best option.
In DoWork you should update databases and send requests.
Besides worker supports long-running workers
Example DownloadWorker:
class DownloadWorker(context: Context, parameters: WorkerParameters) :
CoroutineWorker(context, parameters) {
private val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as
NotificationManager
override suspend fun doWork(): Result {
val inputUrl = inputData.getString(KEY_INPUT_URL)
?: return Result.failure()
val outputFile = inputData.getString(KEY_OUTPUT_FILE_NAME)
?: return Result.failure()
// Mark the Worker as important
val progress = "Starting Download"
setForeground(createForegroundInfo(progress))
download(inputUrl, outputFile)
return Result.success()
}
private fun download(inputUrl: String, outputFile: String) {
// Downloads a file and updates bytes read
// Calls setForeground() periodically when it needs to update
// the ongoing Notification
}
// Creates an instance of ForegroundInfo which can be used to update the
// ongoing notification.
private fun createForegroundInfo(progress: String): ForegroundInfo {
val id = applicationContext.getString(R.string.notification_channel_id)
val title = applicationContext.getString(R.string.notification_title)
val cancel = applicationContext.getString(R.string.cancel_download)
// This PendingIntent can be used to cancel the worker
val intent = WorkManager.getInstance(applicationContext)
.createCancelPendingIntent(getId())
// Create a Notification channel if necessary
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createChannel()
}
val notification = NotificationCompat.Builder(applicationContext, id)
.setContentTitle(title)
.setTicker(title)
.setContentText(progress)
.setSmallIcon(R.drawable.ic_work_notification)
.setOngoing(true)
// Add the cancel action to the notification which can
// be used to cancel the worker
.addAction(android.R.drawable.ic_delete, cancel, intent)
.build()
return ForegroundInfo(notificationId, notification)
}
#RequiresApi(Build.VERSION_CODES.O)
private fun createChannel() {
// Create a Notification channel
}
companion object {
const val KEY_INPUT_URL = "KEY_INPUT_URL"
const val KEY_OUTPUT_FILE_NAME = "KEY_OUTPUT_FILE_NAME"
}
}

WorkManager in Android to handle producer/consumer pattern for data received in FCM

I'd like to know a workaround to create a producer/consumer pattern in my Android application:
I have a dedicated device having a thermal printer, this app receives push notifications from FCM and print a receipt as soon as they arrive. Here it is the issue: multiple notifications at same time are not managed well, some are printed and some other not.
Printing is a call to startActivity(...) with an Intent containing an ACTION_VIEW with a Uri to open that allows printer service (external and not managed by me) to wake up.
So, I thought to create the well known producer/consumer pattern to enqueue all my Intent objects instead of calling startActivity inside FCM's onMessageReceived(...). How can I achieve that? What kind of service should it be implemented to consume this queue and send synchronously prints through these Intents?
I read docs on WorkManager APIs and I'm trying to write something like this below:
MyFirebaseMessagingService.kt
class MyFirebaseMessagingService : FirebaseMessagingService() {
override fun onMessageReceived(remoteMessage: RemoteMessage) {
remoteMessage.data.isNotEmpty().let { _ ->
try{
val content =
remoteMessage.data["content"]?.let { it1 -> Json.parseToJsonElement(it1).jsonObject }
content?.let { it ->
val title = it["title"]?.toString() ?: "Title example"
val body = it["message"]?.toString() ?: "Msg example"
val pushId = it["notificationId"]?.toString() ?: "42"
val data = it["data"]?.jsonObject
val intent = sendToPrinterIntent(data)
sendNotification(..., intent)
//startActivity(intent) //TODO add producer-consumer queue
PrintingWorker.enqueueWork(this, intent)
}
} catch (e: Exception){
Log.d("pushMessage", "Error in json data: $e")
}
}
}
private fun sendToPrinterIntent(data: JsonObject?): Intent {
return data?.let {
val body = getBody(it)
val uri = "customschema://q?text=$body"
return Intent(Intent.ACTION_VIEW, Uri.parse(uri))
} ?: Intent(Intent.ACTION_VIEW, Uri.parse("customschema://q?text="))
}
override fun onNewToken(token: String) {
Log.d("FCMtoken", "Refreshed token: $token")
}
private fun sendNotification(
messageBody: String,
messageTitle: String,
pushId: Int,
pendingIntent: Intent
) {
...
}
}
PrintingWorker.kt
class PrintingWorker(private val appContext: Context, workerParams: WorkerParameters) :
CoroutineWorker(appContext, workerParams) {
override suspend fun doWork(): Result {
//calls start activity and waits for it to finish
return withContext(Dispatchers.IO){
//appContext.startActivity()
workerParams.inputData.keyValueMap.forEach {
println("key: ${it.key} value: ${it.value}")
}
//setForeground()
Result.success()
}
}
override suspend fun getForegroundInfo(): ForegroundInfo {
return try {
ForegroundInfo(NOTIFICATION_ID,createNotification())
} catch (e: Exception) {
ForegroundInfo(NOTIFICATION_ID,Notification()) //example: can be ignored
}
}
private fun createNotification(): Notification {
return NotificationCompat...
}
companion object{
val TAG = "PrintingWorker"
val NOTIFICATION_ID = 4242
fun enqueueWork(context: Context, workData: Intent) {
val workRequest = OneTimeWorkRequest.Builder(PrintingWorker::class.java)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.setInputData(workDataOf(Pair("printingIntent",workData)))
.build()
WorkManager.getInstance(context).enqueue(workRequest)
}
}
}
As you can see in PrintingWorker, I'm not sure on how to let the WorkManager schedules and consumes the enqueued Intents. Idea of using this APIs is to allow consuming queue even device reboots, for example.
Any suggestions?
[EDIT] After reviewing possible solutions, I plan to achieve my goal by using Room + Foreground Service:
idea is to create entries in a table of the Room DB when a notification arrives in FCM's onReceiveMessage -> then a ForegroundService consume entries (deleting one at a time after printing data in it) by using Flow or something like that. Is it a more suitable solution? If yes, what should it be the right procedure to use Flow (or LiveData) to do so, avoiding unwanted results?
You'd need to convert the Bundle from Intent workData to Data data ...with Data.Builder.

Android WorkManager observe progress

I'm using WorkManager for deferred work in my app.
The total work is divided into a number of chained workers, and I'm having trouble showing the workers' progress to the user (using progress bar).
I tried creating one tag and add it to the different workers, and inside the workers update the progress by that tag, but when I debug I always get progress is '0'.
Another thing I noticed is that the workManager's list of work infos is getting bigger each time I start the work (even if the workers finished their work).
Here is my code:
//inside view model
private val workManager = WorkManager.getInstance(appContext)
internal val progressWorkInfoItems: LiveData<List<WorkInfo>>
init
{
progressWorkInfoItems = workManager.getWorkInfosByTagLiveData(TAG_SAVING_PROGRESS)
}
companion object
{
const val TAG_SAVING_PROGRESS = "saving_progress_tag"
}
//inside a method
var workContinuation = workManager.beginWith(OneTimeWorkRequest.from(firstWorker::class.java))
val secondWorkRequest = OneTimeWorkRequestBuilder<SecondWorker>()
secondWorkRequest.addTag(TAG_SAVING_PROGRESS)
secondWorkRequest.setInputData(createData())
workContinuation = workContinuation.then(secondWorkRequest.build())
val thirdWorkRequest = OneTimeWorkRequestBuilder<ThirdWorker>()
thirdWorkRequest.addTag(TAG_SAVING_PROGRESS)
thirdWorkRequest.setInputData(createData())
workContinuation = workContinuation.then(thirdWorkRequest.build())
workContinuation.enqueue()
//inside the Activity
viewModel.progressWorkInfoItems.observe(this, observeProgress())
private fun observeProgress(): Observer<List<WorkInfo>>
{
return Observer { listOfWorkInfo ->
if (listOfWorkInfo.isNullOrEmpty()) { return#Observer }
listOfWorkInfo.forEach { workInfo ->
if (WorkInfo.State.RUNNING == workInfo.state)
{
val progress = workInfo.progress.getFloat(TAG_SAVING_PROGRESS, 0f)
progress_bar?.progress = progress
}
}
}
}
//inside the worker
override suspend fun doWork(): Result = withContext(Dispatchers.IO)
{
setProgress(workDataOf(TAG_SAVING_PROGRESS to 10f))
...
...
Result.success()
}
The setProgress method is to observe intermediate progress in a single Worker (as explained in the guide):
Progress information can only be observed and updated while the ListenableWorker is running.
For this reason, the progress information is available only till a Worker is active (e.g. it is not in a terminal state like SUCCEEDED, FAILED and CANCELLED). This WorkManager guide covers Worker's states.
My suggestion is to use the Worker's unique ID to identify which worker in your chain is not yet in a terminal state. You can use WorkRequest's getId method to retrieve its unique ID.
According to my analysis I have found that there might be two reasons why you always get 0
setProgress is set just before the Result.success() in the doWork() of the worker then it's lost and you never get that value in your listener. This could be because the state of the worker is now SUCCEEDED
the worker is completing its work in fraction of seconds
Lets take a look at the following code
class Worker1(context: Context, workerParameters: WorkerParameters) : Worker(context,workerParameters) {
override fun doWork(): Result {
setProgressAsync(Data.Builder().putInt("progress",10).build())
for (i in 1..5) {
SystemClock.sleep(1000)
}
setProgressAsync(Data.Builder().putInt("progress",50).build())
SystemClock.sleep(1000)
return Result.success()
}
}
In the above code
if you remove only the first sleep method then the listener only get the progres50
if you remove only the second sleep method then the listener only get the progress 10
If you remove both then the you get the default value 0
This analysis is based on the WorkManager version 2.4.0
Hence I found that the following way is better and always reliable to show the progress of various workers of your chain work.
I have two workers that needs to be run one after the other. If the first work is completed then 50% of the work is done and 100% would be done when the second work is completed.
Two workers
class Worker1(context: Context, workerParameters: WorkerParameters) : Worker(context,workerParameters) {
override fun doWork(): Result {
for (i in 1..5) {
Log.e("worker", "worker1----$i")
}
return Result.success(Data.Builder().putInt("progress",50).build())
}
}
class Worker2(context: Context, workerParameters: WorkerParameters) : Worker(context,workerParameters) {
override fun doWork(): Result {
for (i in 5..10) {
Log.e("worker", "worker1----$i")
}
return Result.success(Data.Builder().putInt("progress",100).build())
}
}
Inside the activity
workManager = WorkManager.getInstance(this)
workRequest1 = OneTimeWorkRequest.Builder(Worker1::class.java)
.addTag(TAG_SAVING_PROGRESS)
.build()
workRequest2 = OneTimeWorkRequest.Builder(Worker2::class.java)
.addTag(TAG_SAVING_PROGRESS)
.build()
findViewById<Button>(R.id.btn).setOnClickListener(View.OnClickListener { view ->
workManager?.
beginUniqueWork(TAG_SAVING_PROGRESS,ExistingWorkPolicy.REPLACE,workRequest1)
?.then(workRequest2)
?.enqueue()
})
progressBar = findViewById(R.id.progressBar)
workManager?.getWorkInfoByIdLiveData(workRequest1.id)
?.observe(this, Observer { workInfo: WorkInfo? ->
if (workInfo != null && workInfo.state == WorkInfo.State.SUCCEEDED) {
val progress = workInfo.outputData
val value = progress.getInt("progress", 0)
progressBar?.progress = value
}
})
workManager?.getWorkInfoByIdLiveData(workRequest2.id)
?.observe(this, Observer { workInfo: WorkInfo? ->
if (workInfo != null && workInfo.state == WorkInfo.State.SUCCEEDED) {
val progress = workInfo.outputData
val value = progress.getInt("progress", 0)
progressBar?.progress = value
}
})
The reason workManager's list of work infos is getting bigger each time the work is started even if the workers finished their work is because of
workManager.beginWith(OneTimeWorkRequest.from(firstWorker::class.java))
instead one need to use
workManager?.beginUniqueWork(TAG_SAVING_PROGRESS, ExistingWorkPolicy.REPLACE,OneTimeWorkRequest.from(firstWorker::class.java))
You can read more about it here

How can I run a service the would have access to my database and could run operations on it, but won't stuff up the UI thread

I am building an app the needs to go through a collection of photos stored locally, which I import to a room database, and try to detect for each if it contains faces or not.
I've got everything sorted, my only issue is how to run this operation, which could take a while, in a service that wouldn't stuff up the UI thread.
At first I wanted to use a JobIntentService but couldn't because I was unable to observeForever on a background thread, and couldn't use a simple observer because I have no lifecycleOwner to give to the Observer.
I've ended up using just a service, as soon as the operation starts my UI is pretty much stuck and if I try to do anything the app crashes.
I tried maybe IntentService but I can't use the observer in onHandleIntent because it's a worker thread and it doesn't let me, and when I run the operations under onStartCommand then it's just the same thing.
I feel like I am stuck with the architecture of this thing, I'd appreciate any ideas. Thank you.
This is my service:
class DetectJobIntentService : Service() {
private val TAG = "DetectJobIntentServi22"
lateinit var repo: PhotoRepository
lateinit var observer : Observer<MutableList<Photo>>
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val options = FirebaseVisionFaceDetectorOptions.Builder()
.setClassificationMode(FirebaseVisionFaceDetectorOptions.ACCURATE)
.setClassificationMode(FirebaseVisionFaceDetectorOptions.ALL_CLASSIFICATIONS)
.setMinFaceSize(0.15f)
.build()
val detector = FirebaseVision.getInstance()
.getVisionFaceDetector(options)
repo = PhotoRepository(application)
observer = Observer {
for (file in it) {
val image = FirebaseVisionImage.fromFilePath(application, Uri.parse(file.uri))
AsyncTask.execute {
detector.detectInImage(image).addOnSuccessListener { list ->
if (list.isNotEmpty()) {
file.hasFaces = 1
repo.update(file)
} else {
file.hasFaces = 2
repo.update(file)
}
}
}
}
}
repo.getAllPhotos().observeForever(observer)
val notificationIntent= Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, 0)
val notification = NotificationCompat.Builder(this, getString(tech.levanter.anyvision.R.string.channel_id))
.setContentTitle("Detecting faces..")
.setContentText("64 photos detected")
.setSmallIcon(tech.levanter.anyvision.R.drawable.ic_face)
.setContentIntent(pendingIntent)
.build()
startForeground(1, notification)
return START_NOT_STICKY
}
override fun onDestroy() {
super.onDestroy()
repo.getAllPhotos().removeObserver(observer)
}
}
Seeing your code is in Kotlin, I'll advice you try out Kotlin Coroutines. This would enable you dispatch expensive operations i.e. querying databases, making network requests/calls off to other threads thereby not blocking the UIThread. Coroutines help you avoid the hassle of callbacks. Also, Google just deprecated the AsyncTask API in favour of Coroutines as the way to go for multi-threading purposes.

Worker manager: not start work in enqueue

I have such code, I need to implement the task queue, if the task is in the queue, then you do not need to add it.
I implemented as shown in when, everything works, but sometimes the state of the worker remains ENQUEUED, and new tasks are not added to the queue.
That is, when there is no Internet, I add a task, when the Internet appears, tasks begin to run out, but for some reason, sometimes it doesn’t happen, I can’t understand why the task does not start despite the fact that the Internet is there and the task is in the queue.
How can you determine why the task will not start?
Does anyone have a better suggestion?
//run task
runOneTimeWorkByType<GetDocumentsWorker>(GET_DOCUMENTS_TAG)
private inline fun <reified W : Worker> runOneTimeWorkByType(tag: String) {
val workerInfoList = workManager
.getWorkInfosByTag(tag)
.get()
for (item in workerInfoList) {
if (item.state == WorkInfo.State.ENQUEUED){
return
}
}
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val workRequest =
OneTimeWorkRequestBuilder<W>()
.setConstraints(constraints)
.addTag(tag)
.build()
workManager.enqueue(workRequest)
}
class GetDocumentsWorker(ctx: Context, workerParams: WorkerParameters) :
Worker(ctx, workerParams) {
#Inject
lateinit var serviceUtils: ServiceUtils
init {
App.appComponent.inject(this)
}
override fun doWork(): Result {
Log.d("workmng", "GetDocumentsWorker: start")
try {
serviceUtils.documentsGet()
} catch (e: Exception) {
Log.d("workmng", "GetDocumentsWorker: exception", e.cause)
return Result.retry()
}
Log.d("workmng", "GetDocumentsWorker: end")
return Result.success()
}
}
UPDATE:
I tried to start the task without conditions, but in this case, nothing starts either, have ideas why so?
fun runGetDocumentsTask() {
val workRequest =
OneTimeWorkRequestBuilder<GetDocumentsWorker>()
.addTag(GET_DOCUMENTS_TAG)
.build()
workManager.enqueue(workRequest)
}
Everything starts to work fine when I cancel jobs: workManager.cancelAllWork()
When creating a worker, I run several periodic tasks, can there be a problem in them? If so, how to fix it?
private var workManager: WorkManager = WorkManager.getInstance(ctx)
init {
//workManager.cancelAllWork()
runSendAllPeriodicTasks()
}
private fun runSendAllPeriodicTasks() {
runOneTimeWorkOnPeriod<SendAllWorker>(15, TimeUnit.MINUTES)
runOneTimeWorkOnPeriod<FailureFilesResendWorker>(3, TimeUnit.HOURS)
runOneTimeWorkOnPeriod<GetItemsWorker>(1, TimeUnit.HOURS)
}

Categories

Resources