I use this fragment of code in an activity to display a timer countdown:
//SET COUNTDOWN TIMER in onCreate()
//var tempo is a :Long number
var textView = findViewById<TextView>(R.id.countdownTimer)
object : CountDownTimer(tempo, 1000) {
override fun onTick(millisUntilFinished: Long) {
textView.setText("Remaining time: "+tim(millisUntilFinished) )
}
override fun onFinish() {
textView.text = "Times up!"
}
}.start()
If possible I'd like to implement the possibility to pause this CountDownTimer in onBackPressed() and resume it again?
override fun onBackPressed() {
AlertDialog.Builder(this).apply {
setTitle("Confirm")
setMessage("Exit app?")
setPositiveButton("Yes") { _, _ ->
// if user press yes, cancel timer?
super.onBackPressed()
}
setNegativeButton("No"){_, _ ->
// if user press no, then return the activity and resume timer?
}
setCancelable(true)
}.create().show()
}
My idea would be to edit var tempo differentitate it everytime OnBackPressed and copy the section of this 1st code piece in onCreate() and put it onResume().
I'm quite confused on using this tbh. MAybe there's a faster and better way?
You cannot pause CountDownTimer. What you can do is stop it and then start another one (with the remaining time) when you want to resume it.
Related
I just want to delay a task in a fragment and if the app goes to the background while the delay is running the scope should never resume when the app comes to the foreground:
With following 2 approaches both will execute once the app comes back again, but I want that this never returns once the app was in the background. How to achieve that?
lifecycleScope.launch {
lifecycle.whenResumed {
Timber.d("before delay 1")
delay(15000)
Timber.d("after delay 1")
}
}
lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
Timber.d("before delay 2")
delay(15000)
Timber.d("after delay 2")
}
}
kotlinx.coroutines.delay()
A Job launched by lifecycleScope will not be canceled until lifecycle destroy
https://developer.android.com/reference/kotlin/androidx/lifecycle/LifecycleCoroutineScope
If you need to run it once you have to write something like this
private var delayedJob: Job? = null
override fun onResume() {
if(delayedJob == null) {
delayedJob = lifecycleScope.launch {
Timber.d("before delay")
delay(15000)
Timber.d("after delay")
}
}
}
override fun onPause() {
delayedJob?.cancel()
}
There is a task: the application has a CountDownTimer. I need to make sure that the user sees a message (for example, it could be Toast messsage) when the timer expires. The timer depends on real time, so even if the user does not use the application, the timer still runs. However, there is a condition - if the timer ended exactly when the user was on the fragment, then this message is shown to him. If the user has entered the application after the timer has expired, the message will not be shown to him. How can this be implemented?
There are other ways to do it, but this would be my approach.
When you start the timer, persist the end time (such as storing it in SharedPreferences).
When your fragment comes on screen, or when you start the timer, use this persisted time to determine if there is a future timer end time. If there is, start counting down with a CountDownTimer or coroutine, cancelling any previous timer that might have been set.
Cancel the timer whenever the fragment goes off screen.
I would typically persist the end time in a ViewModel, but to keep this example simple, I'll just do it in the Fragment.
private const TIMER_END_TIME_KEY = "timerEndTime"
class MyFragment: Fragment() {
// ...
private var countDownJob: Job? = null
// Only safe while fragment attached
private val sharedPreferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(requireContext())
private fun startNewCountDown(durationMillis: Long) {
val endTime = System.currentTimeMillis() + durationMillis
sharedPreferences.edit {
putLong(TIMER_END_TIME_KEY, endTime)
}
prepareCountDownCallback()
}
private fun cancelCountDown() {
countDownJob?.cancel()
countDownJob = null
sharedPreferences.edit {
putLong(TIMER_END_TIME_KEY, 0L)
}
}
private fun prepareCountDownCallback() {
countDownJob?.cancel()
val now = System.currentTimeMillis()
val endTime = sharedPreferences.getLong(TIMER_END_TIME_KEY, 0L)
countDownJob = if (endTime > now) {
viewLifecycleOwner.lifecycleScope.launch {
delay(endTime - now)
// Show end of timer message
}
} else {
null
}
}
override fun onPause() {
super.onPause()
countDownJob?.cancel()
countDownJob = null
}
override fun onResume() {
super.onResume()
prepareCountDownCallback()
}
}
I want to start a timer (i think in this case CountDownTimer) as soon as I get a specific wakelock. Once the countdown timer finishes, i want to display an alert dialog. When the wakelock is released, the timer should be killed. When the timer is running and I get a user activity, I want to kill the previous timer and start a new one (or maybe reset this one)
What would be the best way for me to implement this?
Yes you can do it, the only thing you need to do is like this -->
`val dialogListener = DialogInterface.OnClickListener { dialog, which ->
when (which) {
BUTTON_POSITIVE -> {
}
DialogInterface.BUTTON_NEGATIVE -> {
}
}
}
val dialog = AlertDialog.Builder(this)
.setTitle(“YOUR TITLE HERE”)
.setMessage(“YOUR MESSAGE HERE)
.setPositiveButton(“TEXT OF BUTTON”)
.setNegativeButton(“TEXT OF BUTTON”)
.create()
dialog.setOnShowListener(object : OnShowListener {
private val AUTO_DISMISS_MILLIS = 5000 //Timer in this case 5 Seconds
override fun onShow(dialog: DialogInterface) {
//if you want to have stuff done by your buttons it's going go be here with a respective call like (dialog as AlertDialog).getButton(The button you want positive or negative)
then everything you want to happen on it
*************************************************
//here is the timer associated to the button
val defaultButton: Button =
dialog.getButton(AlertDialog.BUTTON_POSITIVE)
val positiveButtonText: CharSequence = defaultButton.text
object : CountDownTimer(AUTO_DISMISS_MILLIS.toLong(), 100) {
override fun onTick(millisUntilFinished: Long) {
defaultButton.text = java.lang.String.format(
Locale.getDefault(), "%s (%d)",
positiveButtonText,
TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished) + 1
)
}
override fun onFinish() {
//everything you wanna do on the moment the timer is off is going to happen here if you wanna open another dialog you need to call it here
}
}.start()
}
})
dialog.show()
}
`
In my code, I have a time-out functionality and I want to use a countdown timer but after a lot of research, I couldn't find similar functionality as a countdown timer in Kotlin coroutine (able to start, cancel and catch finish callback). Then I decided to use GlobalScope.launch. I know this is a bad solution but my code is working perfectly.
Here is my code
viewModelScope.launch {
val timer = object: CountDownTimer(Constants.PAYMENT_TIMER, 1000) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
GlobalScope.launch {
_eventFlow.emit(UIPaymentEvent.NavigateToBack)
}
}
}
timer.start()
collectPaymentIntentUseCase.invoke(currentPaymentIntent!!).onEach { result ->
when (result) {
is Resource.Success -> {
timer.cancel()
if (result.data?.exception == null) {
My question is how can find a 100% similar function to avoid using GlobalScope but be able to use the countdown timer (start, cancel,onComplete callback)?
Note: I am using GlobalScope.lanch to be able to emit UIPaymentEvent.NavigateToBack event to my view
You don't need a CountDownTimer here. Just use the delay() suspend function.
viewModelScope.launch {
val job = launch {
delay(Constants.PAYMENT_TIMER) // Wait for timeout
_eventFlow.emit(UIPaymentEvent.NavigateToBack)
}
collectPaymentIntentUseCase.invoke(currentPaymentIntent!!).onEach { result ->
when (result) {
is Resource.Success -> {
job.cancel() // Cancel the timer
if (result.data?.exception == null) {
You can use callbackFlow for listen your timer. I just code this editor. I hope it will be helpful.
fun timerFlow() = callbackFlow<UIPaymentEvent> {
val timer = object : CountDownTimer(10, 1000) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
CoroutineScope().launch {
_eventFlow.emit(UIPaymentEvent.NavigateToBack)
}
}
}
timer.start()
awaitClose()
}
Coroutines are launched inside a CoroutineScope which are similar to lifecycle for android. As such, Android automatically provide coroutine's scope for components like activity or fragment and bound them to theirs lifecycle.
While it's not recommended to use the global scope that starts and ends with android's process. There are no restriction on creating your own and limiting it to a specific view of time. Creating one starts its life and cancelling it stops all tasks inside.
In your case a countdown can be done with only coroutines. As stated in this answer.
But without changing too much of your existing code you could reuse the viewModelScope that launched your timer to emit your event.
viewModelScope.launch {
_eventFlow.emit(UIPaymentEvent.NavigateToBack)
}
Beware of the life of your scope. If the viewmodelScope is dead when the timer finish, the event will never be sent.
I use Handler for creating a timer in a Widget.
I use the recommended constructor, i.e. passing a Looper to it.
private val updateHandler = Handler(Looper.getMainLooper())
#RequiresApi(Build.VERSION_CODES.Q)
private val runnable = Runnable {
updateDisplay()
}
#RequiresApi(Build.VERSION_CODES.Q)
private fun updateDisplay () {
updateHandler?.postDelayed(runnable, TIMER_MS)
// some other code
}
The TIMER MS is set to 3000 ms.
The timer runs fine for a while and execute the given code. However after a random time elapsed the timer stops working and no more execution of the given code happens.
Please advise what the problem could be ond how to fix it.
Alternatively, can I use some other timer? (The timer should go off every few second - this is the reason why I use Handler)
Thank you for any advice in advance
You could always try using a Coroutine for something like this:
class TimedRepeater(var delayMs: Long,
var worker: (() -> Unit)) {
private var timerJob: Job? = null
suspend fun start() {
if (timerJob != null) throw IllegalStateException()
timerJob = launch {
while(isActive) {
delay(delayMs)
worker()
}
}
}
suspend fun stop() {
if (timerJob == null) return
timerJob.cancelAndJoin()
timerJob = null
}
}
suspend fun myStuff() {
val timer = Timer(1000) {
// Do my work
}
timer.start()
// Some time later
timer.stop()
}
I haven't tested the above, but it should work well enough.
You can use CountDownTimer from Android framework to achieve the same. It internally uses Handler for timer
val timer = object: CountDownTimer(1000,1000){
override fun onTick(millisUntilFinished: Long) {
}
override fun onFinish() {
}
}
timer.start()