How to make a guaranteed real-time stopwatch Android - android

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.

Related

I want to send my location to the server each 1 minute even the app in the background in android?

what is the best practice to call method each 1 minute even the app in the background(android),
I am using Handler(Looper.getMainLooper()) to do it in forground but I need to call method even the app in the background
private fun runScheduleMethods() {
mainHandler.post(object : Runnable {
override fun run() {
viewModel.loadCurrentLocation(locationRequest)
mainHandler.postDelayed(this, 1000 * 60)
}
})
}
use foreground service with notification(because if notification is active then android OS will not kill it)
here is blog
good luck..!
https://medium.com/#ramkumarv3/foreground-service-with-notification-channel-ac6697c8a6d1

Android workmanager minimum interval workaround

I am using the workmanager to run a service in the background, minimum interval from documentation is 15 minutes, but i would like to have an interval of 2 minutes.
I tried this code, it is working while debugging from android studio and runs properly every two minutes, but once i unplug the cable, or deploy it directly via apk, it runs only one time and stops.
In main activity onCreate/onResume :
OneTimeWorkRequest oneTimeRequest = new OneTimeWorkRequest.Builder(CallAndSmsWork.class).addTag(TAG).setInitialDelay(15, TimeUnit.SECONDS).build();
WorkManager.getInstance(getApplicationContext()).enqueueUniqueWork("CallnSms", ExistingWorkPolicy.REPLACE, oneTimeRequest);
In the end of DoWork method of CallAndSmsWork service :
OneTimeWorkRequest oneTimeRequest = new OneTimeWorkRequest.Builder(CallAndSmsWork.class).setInitialDelay(120, TimeUnit.SECONDS).addTag(TAG).build();
WorkManager.getInstance(getApplicationContext()).enqueueUniqueWork("CallnSms", ExistingWorkPolicy.REPLACE, oneTimeRequest);
Is there anything i did wrong, or any other option to have the service running properly every two minutes ?
P.S.: i am on android 10 , one plus 6t
For a 2 min interval you could use a periodic runnable, something like:
private val runnable = object : Runnable {
val periodInMillis = 2 * 60 * 1000L
override fun run() {
Log.d(DEBUG_TAG,"Runnable scheduled")
// Do stuff
mHandler?.postDelayed(this,periodInMillis)
}
}
And send it to a handler thread:
//Threading madness
private var mThread: HandlerThread? = null
private var mHandler: Handler? = null
Than you can initialize:
//Prepare Thread
if (mThread == null) {
mThread = HandlerThread("HeartBeatThread", Process.THREAD_PRIORITY_BACKGROUND)
mThread!!.start()
}
if (mHandler == null) mHandler =
Handler(mThread!!.looper) //Blocks until looper is prepared, which is fairly quick
mHandler!!.post(runnable)
In this way you have a task that enqueues itself and runs every 2 minutes. The issue with that could be Doze mode and something to do with the way that Android handles battery saver mode. For that you can pick up a lock I guess.

How to create a Singleton timer with a coroutine for a Kotlin Android app with various fragments?

In my android app, which I made in Kotlin, I used a Timer and a Runnable to repeat a certain set of tasks.
However, I quickly ran into issues, as my app had a bottom navigation and therefore, everytime I clicked on a different navigation item like (home, profile, settings, etc) and returned back to the fragment with the Runnable, the timer was behaved erratically. It seemed to me that multiple timers were being created and somehow, were not being destroyed.
A quick solution was to use a Singleton so initialize one instance with a timer and synchronize it with whatever function I want it to repeat.
However, coming from Java I had no knowledge of using a Singleton in Kotlin. I did some research and found out that to make a singleton, you declare it as object SingletonName and you just use it. It's that simple (as mentioned by various dev blogs)
So, I basically want a timer to be in a singleton and then repeat a certain amount of tasks in a fragment with a runnable, without ever having to maintain another timer.
How do I achieve it?
Creating a Singleton was easy. As easy as writing
object SyncTimer {
//TODO:Your code here
}
Since, I was doing UI operations, I wanted to use a Co-routine, which will be maintained in the Singleton itself.
So I added the dependency in my build.gradle (app level)
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2"
Next up, we create the co-routine with the runnable. For my usage, I'll initialize the runnable in my Dashboard.kt file. The Dashboard activity contains all my other fragments in the bottom navigation (home, profile, settings, etc.)
object SyncTimer{
//You can supply your own runnable from Fragments
private lateinit var userRunnable: Runnable
fun init (){
val handler = Handler()
GlobalScope.launch {
if(userRunnable == null){
userRunnable = Runnable {
Log.d("SyncTimer", "Working without any inputs")
}
}
val swipeTimer = Timer()
swipeTimer.schedule(object : TimerTask() {
override fun run() {
handler.post(updateUi())
}
}, 1000, 2000)
}
}
fun set(runnable : Runnable) {
Log.d("SyncTimer", "A runner was provided")
userRunnable = runnable
}
private fun updateUi(): Runnable{
return userRunnable
}
}
Then in my Dashboard activity, I initialize the Runnable like in the onCreate()
SyncTimer.init()
With this, the timer and runnable have initialized. My dashboard quickly defaults to the HomeFragment, where in the onCreateView() I pass a runnable of my own to the set():
val updateUi = Runnable {
//Your code here
}
SyncTimer.set(updateUi) // <- Pass your runnable here
If you have noticed, I've used an if statement in the singleton, where if userRunnable == null then pass a default runnalbe, that prints into the logs, "Working without any inputs"
However, when my app starts, I always get "A runner was provided" in the logCat. This means that the Runnable worked right from the first instance, with the runnable I passed on to it with the SyncTimer.set()
Now, even if I change to different fragments, the timer is maintained. and Only when I return to the HomeFragment the code to update ui is called again.
Therefore the cleanest version of the code is:
object SyncTimer{
private lateinit var userRunnable: Runnable
fun init (){
val handler = Handler()
GlobalScope.launch {
val swipeTimer = Timer()
swipeTimer.schedule(object : TimerTask() {
override fun run() {
handler.post(updateUi())
}
}, 1000, 2000)
}
}
fun set(runnable : Runnable) {
Log.d("SyncTimer", "A runner was provided")
userRunnable = runnable
}
private fun updateUi(): Runnable{
return userRunnable
}
}
Initialize with
SyncTimer.init()
Pass a runnable
val runnable = Runnable {
//TODO: Do something
}
The beauty of this code is that you can pass different runnables in different fragments and they'll all work without any new timers being created.
Improve upon this code:
How do you create different timers with different delays and periods?

Android: How to call function inside Application class every 5 minutes in background

I want to start refresh Service within my app. What I want to achieve is to do API call every 5 minutes even if user locked his screen to update data and especially visible Notification by recreating Notification with new data from API call.
I tried to move my logic to Application class where I will initialize Job within GlobalScope, which will run indefinitely until I cancel this Job. This solution works if I set delay to 10 or 30 seconds. It is working even if my App is in background. But if I set delay to way longer period (which I need in this case) like 5 - 10 minutes, it will suddenly stop. My understanding is that this Job will die down or Application class is destroyed when there is long inactivity.
I would like to create Service which will communicate with my Application class and initialize this Job within Service to call Application class function to refresh Notification. But I cant use parameters in Service.
Is there any way how to link Application class and Service?
I don't need to run this refreshAPI if App is killed.
Example(this is running within Application class - want to move it to Service and call app.callRefreshAPI() from Service class):
var refresher: Job? = null
private var refreshRate = 300000L
fun createNotificationRefresher(){
refresher = GlobalScope.launch {
while (isActive){
callRefreshAPI()
delay(refreshRate)
}
}
}
UPDATE: CountDownTimer solution (not working):
var refresher: CountDownTimer? = null
private var refreshRate = 300000L //5min
private var refresherDuration = 780000L //12min
fun initNotificationRefresher(){
refresher = object : CountDownTimer(refresherDuration, refreshRate) {
override fun onTick(millisUntilFinished: Long) {
callRefreshAPI()
}
override fun onFinish() {
initNotificationRefresher()
}
}.start()
}
UPDATE 2: Timers/jobs/workers are not working when phone screen is locked and OS is in a sleep mode. That means there is no way to use timers in a background-background operations. I had to use BroadcastReceiver registered in Application class (NOT! AndroidManifest) and listen to SCREEN_ON action. Then saving time when user unlocked his phone and checking if it was at least 5-10 minutes between screen lock which updated notification and under this condition refresh Notification by calling API in GlobalScope.
I hope this will be helpful for others. Job/Timer will work if Application is in background and user is still interacting with phone (Checking other App, browsing stuff etc.).
Though calling an API after every 5 minutes is not the most optimized way to do the task.
The minimum value is 15min for periodic jobs.
You can use
Ever Note Android Job Library for that.
private void schedulePeriodicJob() {
int jobId = new JobRequest.Builder(DemoSyncJob.TAG)
.setPeriodic(TimeUnit.MINUTES.toMillis(15), TimeUnit.MINUTES.toMillis(5))
.build()
.schedule();
}
you can use CountDownTimer for that. and Create an IntentService class and Run that service for API call.
JAVA
public void repeatCall(){
new CountDownTimer(50000, 1000) {
public void onTick(long millisUntilFinished) {
}
public void onFinish() {
repeatCall();//again call your method
}
}.start();
}
//Declare timer
CountDownTimer cTimer = null;
//start timer function
void startTimer() {
cTimer = new CountDownTimer(30000, 1000) {
public void onTick(long millisUntilFinished) {
}
public void onFinish() {
}
};
cTimer.start();
}
//cancel timer
void cancelTimer() {
if(cTimer!=null)
cTimer.cancel();
}
KOTLIN
fun repeatCall() {
object : CountDownTimer(50000, 1000) {
override fun onTick(millisUntilFinished: Long) {
}
override fun onFinish() {
repeatCall()//again call your method
}
}.start()
}

How do I include a break time in a countdown timer app for Android?

I'm building an Android app, which is similar to a pomodoro timer. Basically a user can set a work time and a break time and depending on how many sessions that user sets, the timer will initiate the work time, then break time, and then repeat until its reached the number of sessions the user has specified.
What I'm struggling with is how to approach creating a break time. When creating the break time, would it just restart the instance of CountDownTimer that I already have but set with the break time, or should I create a new instance of CountDownTimer? Should I create an array of CountDownTimer that would be something like [work, break, work, break] and then this would replace my single work time that I've created in startTimer()?
More importantly, I'm trying to figure out how to break down problems on my own, so how might I break down thinking about this problem?
Inside of TimerFragment.kt:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
...
// Setup button actions
buttonTimerStartPause?.setOnClickListener {
if (isTimerRunning) {
pauseTimer()
} else {
startTimer()
}
}
private fun startTimer() {
countDownTimer = object : CountDownTimer(timeLeftInMillis,
MILLIS_IN_ONE_SECOND.toLong() - 900) {
override fun onTick(millisUntilFinished: Long) {
timeLeftInMillis = millisUntilFinished
progressBarUpdate?.visibility = View.VISIBLE
progressBarStatic?.visibility = View.INVISIBLE
updateCountDownText()
updateProgressBar()
}
override fun onFinish() {
isTimerRunning = false
updateButtonVisibility()
}
}.start()
isTimerRunning = true
updateButtonVisibility()
}
Expected behaviour:
If the user has set a 20 min work time and 5 min break time for 2 sessions, then the timer will run like this: 20min work, 5min break, 20min work, 5min break, and end.
What I have so far is an app where users can set a single work time, then start, pause or reset that work time. I've done this by using a CountDownTimer.
No it will not create a new instance or not to restart the countdown timer, it just continue from where you stopped it or took a break.
It Defines all methods
Methods

Categories

Resources