Im trying to perform a one time work with the worker class
where the doWork method will just count a number
Call it I.
This will loop through 100 times, and in every iteration, the counter will continue grow up.
I added a constraint for this work to be network Constraining connected.
I would like to count every time and if I close the Network the count will stop.
After I open the network I would like The count to continue work from the same count
value that was there before
Every time I close the network and open it again the count starts from zero again and the value that was in the counter before didn't save in the global variable I that I defined in the worker. it's like not taking the last value that was defined there.
My worker class
class WorkerClass(appContext: Context, workerParams: WorkerParameters):
Worker(appContext, workerParams) {
var i:Int=0
override fun doWork(): Result {
while (i < 100) {
if (this.isStopped){
Log.i("Hello","I Stopped A")
return Result.retry()
}
i++
Log.i("Hello", "The value of counter is :"+i.toString())
Thread.sleep(200)
}
return Result.success()
}
}
My simple work creator: (There is a data passed I don't use it right now so you can ignore it.
val constrints=Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val k = sceondTxt.text.toString()
val data=Data.Builder()
data.putString("Counter Value",k)
val oneReq:WorkRequest= OneTimeWorkRequestBuilder<WorkerClass>().setConstraints(constrints).setInputData(data.build())
.build()
So as you can see I define an I as Integer and every iteration of the work the I is growing. but when I close the connection and open it again the I is 0 again and didn't save from the last run of doWork.
does the definition of i is not global? the system wont take the last value of I that was stored in the doWork for later iterations?
How can I save the value of something in my doWork to later use it again (after the constraints are met) not as the default value?
Related
I'm trying to use a foreground service to implement a count up timer feature in my app, essentially a stopwatch. Below is the general idea of what I'm doing, which works well when the app is in the foreground, but not when the screen is off.
This is because handler.postDelayed() is not guaranteed to be real time, and when the screen is off, it's far from it.
class TimerService: Service(), CoroutineScope {
private var currentTime: Int = 0
private val handler = Handler(Looper.getMainLooper())
private var runnable: Runnable = object : Runnable {
override fun run() {
if (timerState == TimerState.START) {
currentTime++
}
broadcastUpdate()
// Repeat every 1 second
handler.postDelayed(this, 1000)
}
}
private val job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + job
// Call this to start the timer
private fun startCoroutineTimer() {
launch(coroutineContext) {
handler.post(runnable)
}
}
// Rest of the service class
...
}
My question is, what is the cannonical way to do something like this? I need to be able to guarantee, using a foreground service, that I can make an accurate stopwatch that is able to start, pause, resume, and stop.
I have the state broadcasting and everything else worked out already, but the actual timer part is where I'm stuck, and I can't seem to find a simple and efficient solution that is guaranteed to be accurate.
First start with a different OS. There's an entire class of OSes called RTOS (real time OS). Linux (and thus Android) are not one. If you actually need realtime, linux is not an acceptable solution.
But let's say you don't actually need realtime, you just want higher accuracy. There's a few ways to easily improve your code.
The biggest thing is that your current code assumes the timer will go off once per second. Don't do that. Instead, keep track of the time when the timer starts. Each time the timer goes off, get the current time. The time elapsed is the delta. That way, any accumulated inaccuracies get wiped away each tick. That will also fix a lot of your screen off case, as the first update after screen on will update to the correct time elapsed.
private var timeStarted : Long = 0
private var timeElapsed : Long = 0
private var runnable: Runnable = object : Runnable {
override fun run() {
if (timerState == TimerState.START) {
timeElapsed = System.getCurrentTimeMillis() - timeStarted
}
broadcastUpdate()
// Repeat every 1 second
handler.postDelayed(this, 1000)
}
}
private fun startCoroutineTimer() {
timeStarted = System.getCurrentTimeMillis()
handler.post(runnable)
}
Also notice you don't need a coroutine to post to a handler. The handler takes care of multithreading, launching a coroutine there provides no value.
I want to build a work manager that sends users a notification at certain times. The repeating criterias are the following:
a notification every week, on TUESDAY at 14:30
a notification every month, o the 15th of that month, having a repeat count of only 6 times
How can I achieve this?
There is no way to set an exact time so no periodic work is possible. You have to recalculate every time:
Do something like this by using "setInitialDelay":
fun setupWork(){
if (isCountLimitReached()) {
return
}
val workRequest: WorkRequest =
OneTimeWorkRequestBuilder<MyWork>()
.setInitialDelay(getNextInterval(),TimeUnit.MILLISECONDS)
.build()
updateCount()
WorkManager
.getInstance(context)
// Make sure you have only one work like that.
.enqueueUniqueWork(
"MY_WORK_UNIQUE_NAME",
// If there is existing work like this - replace it.
ExistingWorkPolicy.REPLACE,
workRequest
)
}
fun getNextInterval(): Long {
TODO("Get milliseconds from 'execution date' and subtract 'now'")
}
private fun updateCount(){
TODO("Use SharedPreferences or something like this")
}
private fun isCountLimitReached(): Boolean {
TODO("Use SharedPreferences or something like this")
}
Is this possible to execute function in android every minute starting from full minute, so eg. when I run my app at 8:30:12 the first function call will be at 8:31:00 and next at 8:32:00 and so on.
You have to first calculate the time required to become exact minute that is starting the task at 8:30:12, first call should happen at 8:31:00.
So for that you can pull current time using System.currentTimeMillis() and then ceil it's minute value to the nearest integer,
// import kotlin.math.ceil
val firstCallTime = ceil(System.currentTimeMillis() / 60_000.0).toLong() * 60_000
Then you can use coroutines to handle your task that will use a CommonPool to get reusable threads from and won't require to you to create threads.
// scope can be anything, for instance you want to call the function
// only when client is using your application then use viewModelScope provided,
// otherwise your own scope like `val scope = CoroutineScope(Dispatchers.Main)`
// Or use Dispatchers.Default for CPU intensive tasks
val parentJob = scope.launch {
// suspend till first minute comes after some seconds
delay(firstCallTime - System.currentTimeMillis())
while (true) {
launch {
yourFunctionToBeCalled()
}
delay(60_000) // 1 minute delay (suspending)
}
}
// If you ever want your job to cancel just do a cancel,
// the function delay suspends and so cancellable without interrupting the threads
// parentJob.cancel()
Use PeriodicWorkRequest along with WorkManager class in android. It goes like this:
val work = PeriodicWorkRequestBuilder<MyWorker>(60, TimeUnit.SECONDS)
.build();
val workManager = WorkManager.getInstance(context);
workManager.enqueuePeriodicWork(work);
My Android app is listening on a Firebase Database. Whenever my activity becomes inactive, I stop the listener, and when the activity becomes active again, I restart listening. This is done using LiveData and the 'onActive' and 'onInactive' methods as below:
#Override
protected void onActive() {
Log.d(LOG_TAG, "onActive");
query.addValueEventListener(listener);
}
#Override
protected void onInactive() {
Log.d(LOG_TAG, "onInactive");
query.removeEventListener(listener);
}
Using the debugger, I have noticed that when I press the back button and close the app, the onInactive method gets called, and the app goes in the background. When I reopen the app, by picking it among the apps that are in the background, the onActive method gets called. However, in this case, all my data is reread from the database which will consume data bandwidth.
My question is:
What is the best way to avoid the re-reading of the data every time the app is coming back from the background?
Thanks
What you will need to do is set a "timeout" of sorts on your LiveData so that it defers becoming inactive for as much delay as you deem appropriate.
I implemented a "LingeringLiveData" superclass for exactly this situation. You can see it in a project of mine on GitHub. It's written in Kotlin, but you should be able to port it to Java without much trouble.
Subclasses need to provide implementations for startLingering and stopLingering that mirror what you would normally do in onActive and onInactive.
Basically, it sets up a timer to delay a call to endLingering after onInactive has been invoked, but only if onActive isn't invoked before that time expires. This lets you app stop and start without losing the listener.
abstract class LingeringLiveData<T> : LiveData<T>() {
companion object {
private const val STOP_LISTENING_DELAY = 2000L
}
// To be fully unit-testable, this code should use an abstraction for
// future work scheduling rather than Handler itself.
private val handler = Handler()
private var stopLingeringPending = false
private val stopLingeringRunnable = StopLingeringRunnable()
/**
* Called during onActive, but only if it was not previously in a
* "lingering" state.
*/
abstract fun beginLingering()
/**
* Called two seconds after onInactive, but only if onActive is not
* called during that time.
*/
abstract fun endLingering()
#CallSuper
override fun onActive() {
if (stopLingeringPending) {
handler.removeCallbacks(stopLingeringRunnable)
}
else {
beginLingering()
}
stopLingeringPending = false
}
#CallSuper
override fun onInactive() {
handler.postDelayed(stopLingeringRunnable, STOP_LISTENING_DELAY)
stopLingeringPending = true
}
private inner class StopLingeringRunnable : Runnable {
override fun run() {
if (stopLingeringPending) {
stopLingeringPending = false
endLingering()
}
}
}
}
Jetpack LiveData KTX now also offers a liveData convenience constructor that accepts a similar timeout parameter and runs code in a coroutine. You won't be able to use at all from Java, but it's nice to know about.
The easiest way to decrease data downloads in this case is to enable disk persistence in the Firebase client.
With disk persistence enabled, the Firebase client will write any data it gets from the server to a local disk cache, cleaning up older data if the cache gets too big.
When the client is restarted, the client will read the data from disk first, and then only request updates from the server using a so-called delta sync. While this delta sync still transfers data, it should typically be significantly less than the total data at the location you listen on.
I've got an app widget where I want to maintain up-to-date information in a line of text that says "x minutes ago", referring to the age of the bitmap below it. I wrote the code as follows:
private fun scheduleUpdateAge(context: Context) {
val jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
val resultCode = jobScheduler.schedule(
JobInfo.Builder(UPDATE_AGE_JOB_ID, ComponentName(context, UpdateAgeService::class.java))
.setMinimumLatency(MINUTE_IN_MILLIS)
.setOverrideDeadline(2 * MINUTE_IN_MILLIS)
.build())
reportScheduleResult("update age", MINUTE_IN_MILLIS, resultCode)
}
In the last line i check the result of scheduling, it's always successful.
In the job service I update the remote views and then reschedule the job:
class UpdateAgeService : JobService() {
override fun onStartJob(params: JobParameters): Boolean {
MyLog.i("UpdateAgeService start job")
updateRemoteViews(applicationContext)
jobFinished(params, false)
scheduleUpdateAge(applicationContext)
return false
}
override fun onStopJob(params: JobParameters): Boolean {
MyLog.i("UpdateAgeService stop job")
return true
}
}
However, I keep observing that Android has not invoked my job for much longer periods, above ten minutes. At that time the other scheduled job, the one that refreshes the bitmap, kicks in and resets the age text as well.
The documentation on setOverrideDeadline states
Set deadline which is the maximum scheduling latency. The job will be run by this deadline even if other requirements are not met.
but Android seems to violate it.
Am I missing something here?
I'm aware that the "natural" way to schedule periodic jobs is setPeriodic(), but setPeriodic() wasn't invoking my job either and its contracts clearly states
You have no control over when within this interval this job will be executed, only the guarantee that it will be executed at most once within this interval.
So missing the schedule is "by the book" for that case.