Check for network status update while application killed - android

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"
}
}

Related

How to start a startForegroundService using WorkManager

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

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.

Migrate Android Job into Android Work manager

I have implemented Evernote Android Job in my android application. but i want to change it as WorkManager.
JobManager.create(this).addJobCreator(new MyJob());
public class MyJob implements JobCreator {
#Nullable
#Override
public Job create(#NonNull String tag) {
switch (tag) {
case SyncMasterDataJOB.TAG:
return new SyncMasterDataJOB();
}
return null;
}
}
Job Class:
public class SyncMasterDataJOB extends Job {
public static final String TAG = "job_note_sync";
#NonNull
#Override
protected Result onRunJob(#NonNull Params params) {
return Result.SUCCESS;
}
public static void schedulePeriodic() {
try{
new JobRequest.Builder(SyncMasterDataJOB.TAG)
.setPeriodic(15*1000, 5*1000)
.setUpdateCurrent(true)
.build()
.schedule();
} catch (Exception e){
e.printStackTrace();
}
}
How can i change Job into android workmanager.
WorkManager is highly configurable and will allow you to create a PeriodicWorkRequest or a OneTimeWorkRequest these are guaranteed to succeed. PeriodicWorkRequest will fire when you schedule the work, as well as when you have specified in the timer. It will execute in the background even if the app is closed or backgrounded. If you didn't want your task to execute immediately you can use a PWR(PeriodicWorkRequest) with a FlexInterval. See the docs below for more info.
WorkManager Docs
WorkManager Architecture
WorkmManager CodeLab
For example, I created two PeriodicWorkRequests that refresh services and keeps the user logged in always by renewing their token. When the user authenticates the PeriodicWorkRequest is created. In my case, I didn't need it to fire right away as they have just received and cached this information so I utilized the FlexInterval. When the app is backgrounded or closed, the workers continue to refresh services every 12 hours and refresh the token every 6. It works like a charm.
Here is an example:
Build Work:
override fun beginWork() {
val periodicWorkRequest = PeriodicWorkRequest.Builder(
MyWorker::class.java,
REPEAT_INTERVAL, TimeUnit.MINUTES, // How often work should repeat
// Flex not required.
FLEX_INTERVAL, TimeUnit.MINUTES) // Limits execution into a time window
.setConstraints(
Constraints.Builder().setRequiredNetworkType(
NetworkType.CONNECTED).build())
.addTag(MY_WORKER_TAG)
.build()
WorkManager.getInstance().enqueueUniquePeriodicWork(
MY_UNIQUE_WORK,
ExistingPeriodicWorkPolicy.KEEP,
periodicLoginRequest)
Worker:
class MyWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
// DO WORK HERE
Result.success()
} else {
// HANDLE FAILURE HERE
Result.failure()
}
The above is a simple implementation, but it should give you the general idea.

JobService does not repeat

In Android Oreo, I want to create a service that periodically updates data from the network.
class NetworkJobService : JobService() {
override fun onStopJob(p0: JobParameters?): Boolean {
jobFinished(p0,true)
return true
}
override fun onStartJob(p0: JobParameters?): Boolean {
//Average working time 3 to 5 minutes
NetworkConnect.connect()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doFinally {
jobFinished(p0,true)
}
.subscribe({result->
// Writes the parameters to the cache with the current time.
Cache.write("result : $result")
},{e->
// Writes the parameters to the cache with the current time.
Cache.write(e)
})
return true
}
}
This service is registered in the schedule when you run MainActivity.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
jobSchedule()
button.setOnClickListener { readLog() }
}
val interval = 1000 * 60 * 15L
private fun jobSchedule(){
val jobScheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
val jobInfo = JobInfo.Builder(3,
ComponentName(this, NetworkJobService::class.java))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setPeriodic(interval)
.build()
jobScheduler.schedule(jobInfo)
}
private fun readLog(){
//The log file is read line by line.
Cache.read()
.reversed()
.toObservable()
.subscribe({ text->
Log.i("Service Log",text)
},{
})
}
}
However, when I read the log file and checked the results, the service was running only when the MainActivity was running. In other words, it was not being rescheduled.
1) Run the activity and just turn off the device's screen
2) Run the activity and press the Home button to return to the launcher.
3) When the service is terminated and the app is deleted in the multitasking window
The most I wanted was to work in case 3), but in any of the above, the services I wanted were not rescheduled.
What have I missed?
While working with background threads in Oreo when the app is in killed you need to start the services as foreground service. Here is the details of the same. Essentially, show a notification to make the user aware that your app is trying to doing something in the background.
Hope it helps you.

After reopening application, multiple instances of a service are created

I am trying to create an application to update a persistent notification even while the app is closed. Right now, I'm using a service that is started in MainActivity's onCreate():
serviceIntent = Intent(this, PersistentService::class.java)
val stopped = stopService(serviceIntent)
println("[123] stopped: $stopped")
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent)
} else {
startService(serviceIntent)
}
This works perfectly to start it. However, when I reopen and close the app, even though stopped: true is printed, the previous service is still running and the previous service's stopService() was not called.
Stripped down version of the PersistentService class:
var timesStarted = 0
var lastService: PersistentService? = null
class PersistentService: Service(){
private var timer: Timer? = null
private var task: AsyncTask<*, *, *>? = null
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
timesStarted++
println("[123]starting persistent service. timesStarted: $timesStarted lastService===this: ${lastService===this} lastService==this: ${lastService==this} lastService: $lastService")
println("[123] hashCode: ${hashCode()}")
lastService = this
if(timer == null){
timer = Timer()
}
setToLoadingNotification()
timer?.scheduleAtFixedRate(object : TimerTask(){
override fun run(){
println("[123]Updating hashCode: ${this#PersistentService.hashCode()}")
if(task?.status == AsyncTask.Status.RUNNING){
// send notification saying last request timed out
}
task?.cancel(true)
task = DataUpdaterTask(DatabaseDataRequester { GlobalData.connectionProperties }) { dataRequest ->
// send notification based on dataRequest value
}.execute()
}
}, 1000L, UPDATE_PERIOD)
return START_STICKY
}
private fun notify(notification: Notification){
getManager().notify(NOTIFICATION_ID, notification)
startForeground(NOTIFICATION_ID, notification)
}
private fun getBuilder(): Notification.Builder {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
return Notification.Builder(this, NotificationChannels.PERSISTENT_STATUS.id)
}
return Notification.Builder(this)
}
private fun getManager() = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
override fun stopService(name: Intent?): Boolean {
println("[123]Stopping persistent service")
timer?.cancel() // stop the timer from calling the code any more times
task?.cancel(true) // stop the code from running if it's running
// getManager().cancel(NOTIFICATION_ID)
return super.stopService(name)
}
}
Here is the output
[123] stopped: false
[123]starting persistent service. timesStarted: 1 lastService===this: false lastService==this: false lastService: null
[123] hashCode: 4008007
[123]Updating hashCode: 4008007
[123]Got successful data request
[123]Updating hashCode: 4008007
[123]Got successful data request
// *close and reopen the app*
[123] stopped: true
[123]starting persistent service. timesStarted: 2 lastService===this: false lastService==this: false lastService: me.retrodaredevil.solarthing.android.PersistentService#3d2847
[123] hashCode: 7823272
[123]Updating hashCode: 7823272
[123]Got successful data request
[123]Updating hashCode: 4008007
[123]Got successful data request
[123]Updating hashCode: 7823272
[123]Got successful data request
As you can see from the output, both services are running at the same time. The hashcode shows that they are not the same object so it wouldn't matter if I put my code in the onCreate() instead of onStartCommand() (I already tested this anyway)
It would be helpful if anyone could point me in the right direction. I am new to android development and I had trouble finding the correct way to do this. I'm not even sure if what I'm doing right now is the best way to update the notification.
and the previous service's stopService() was not called.
stopService() is not a lifecycle method of a Service. Perhaps you are thinking of onDestroy().
the previous service is still running
No, the previous service instance was stopped. You just leaked the Timer, because you did not cancel the Timer in onDestroy().
So, override onDestroy(), put your cancel() calls in there, get rid of the rest of stopService(), and you should be in better shape.

Categories

Resources