Editted: Now, I know why. It was because I didn't call it on fragment properly. It's working now!
It is running without error but doesn't work.
I want the timer to be seen in the text view that I made on XML but it doesn't show anything.
This is my ViewModel file
class TotpViewModel: ViewModel() {
private var _time = MutableLiveData<String>()
val time : LiveData<String>
get() = _time
private var mTimer = Timer()
init {
startTimer()
}
private fun startTimer() {
var remainTime : Int = 1800
mTimer = timer(period = 1000) {
remainTime--
_time.postValue("Time : " +
(remainTime / 60.0).toInt().toString().padStart(2,'0') + ":" +
(remainTime % 60).toInt().toString().padStart(2,'0'))
if(remainTime == 0) stopTimer()
}
}
fun stopTimer() {
mTimer.cancel()
}
fun onBackPress() {
}
}
Any help will be greatly appreciated. Thank you
It's because you are calling viewModel.stopTimer() in onViewCreated that cancel timer and stop it when view being displayed
Related
My goal:
in the view of the fragment I have a button that, when pressed once, launches a method in the viewModel which cyclically calls a suspend function to be repeated every few seconds from its conclusion. Pressing the button again stops this cycle.
My approach:
inside the fragment I set the onclicklistener of the button
binding.demoButton.setOnClickListener {
viewModel.toggleDemo()
}
in the viewModel:
private var startDemo : Boolean = false //I need to know whether to start the loop or stop it
private var isBusy : Boolean = false //I need to know if my task is running or finished
fun toggleDemo(){
val oldValue : Boolean = startDemo
val newValue = !oldValue
startDemo = newValue
if(startDemo){
saveLogLine("** start demo **") //method that passes some log strings to the fragment
startDemo()
}else{
saveLogLine("NO start demo!!")
}
}
private fun startDemo(){
GlobalScope.launch(Dispatchers.IO) {
saveLogLineAsync("before while loop")
while(startDemo){
if(!isBusy){
isBusy = true
Handler(Looper.getMainLooper()).postDelayed({
runBlocking(Dispatchers.IO) {
saveLogLineAsync("inside runBlocking")
initDemo()
}
isBusy = false
saveLogLineAsync("inside handler")
}, 5000)
}
}
saveLogLineAsync("after while loop")
}
}
private suspend fun initDemo(){ //my task
}
Is there a more elegant way to do this?
I would have liked to use a Service () or a BroadcastReceiver () but in both cases I would not know how to make them communicate with the fragment or with the viewModel (more precisely, they should be able to use the 2 methods 'saveLogLineAsync' and 'intDemo')
You can simplify your code with this:
private var demoRunning = false
private var demoJob: Job? = null
fun toggleDemo() {
if (!demoRunning) {
startDemo()
} else {
demoJob?.cancel()
}
demoRunning = !demoRunning
}
private fun startDemo() {
demoJob = viewModelScope.launch(Dispatchers.IO) {
while (true) {
initDemo()
delay(5000)
}
}
}
private suspend fun initDemo() { //my task
Log.e("INIT DEMO", "initDemo Ran")
}
I have created StopWatch function for Android app in Kotlin using Timer class. I am using activity?.runOnUiThread to display the time in the App Bar (not in View). Is there any simple way to stop timer and set it back to 0. Is the multithreading necessary?
Here is my function:
private fun stopwatch(isCanceled: Boolean) {
val timer = Timer()
val tt: TimerTask = object : TimerTask() {
override fun run() {
num += 1000L
val runnable = Runnable { setModeTitle(getString(R.string.chipper_title) + TimerUtil.timerDisplay(num)) }
activity?.runOnUiThread(runnable)
}
}
timer.schedule(tt, 0L, 1000)
if (isCanceled) {
//what to implement here, if possible?
}
}
You need to store a resulting TimerTask object and later call cancel() on it. Something like:
private var tt: TimerTask? = null
private fun stopwatch() {
val timer = Timer()
tt = object : TimerTask() {
override fun run() {
num += 1000L
val runnable = Runnable { setModeTitle(getString(R.string.chipper_title) + TimerUtil.timerDisplay(num)) }
activity?.runOnUiThread(runnable)
}
}
timer.schedule(tt, 0L, 1000)
}
private fun cancelStopwatch() {
tt?.cancel()
}
Just make sure not to call stopwatch() twice, without first cancelling an already running stopwatch.
I'm a Kotlin newbee. I am trying to program a countdown timer for an archery countdown clock. First the countdown timer countDownP runs for 10 sec, then runs countdown timer countDown for 60 sec. However the two timers won't run sequentially unless I nest the 2nd timer inside the onFinish() of the 1st timer.
Any suggestions would be most appreciated.
Here's the code
import ...
class MainActivity : AppCompatActivity() {
//initialize clock countdown timers for prep and shoot
internal lateinit var countDownP: CountDownTimer
internal lateinit var countDown: CountDownTimer
internal var isClockRun = false
// set-up initial clock values
internal val endTime: Long = 60000
internal val prepTime: Long = 10000
internal val warn1Time: Long = 45000
internal val totEnds: Int = 10
internal val totPEnds: Int = 3
internal var totLine: Int = 6
internal var totTurn: Int = 3
// set-up initial clock variables
internal var endNo: Int = 1
internal var endPNo: Int = 1
internal var turnNo: Int = 1
internal var isEndP = true
internal var sUntilFinished: Long = 0
//******* PROGRAM CODE STARTS ***********
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//set-up various views on screen
// set-up initial view on starting
// **START BUTTON**
startButton.setOnClickListener {
countDownP = object : CountDownTimer(prepTime, 500) {
init {
}
override fun onTick(msUntilFinished: Long) {
sUntilFinished = msUntilFinished / 1000
resultTextViewTimer.text = "${(sUntilFinished / 60)}:${(sUntilFinished % 60).toString().padStart(2, '0')}"
}
override fun onFinish() {
// when prep countdownP finished, start main countdown
isClockRun = false
isEndP = false
}
}
countDown = object : CountDownTimer(endTime, 500) {
init {
isClockRun = true
}
override fun onTick(msUntilFinished: Long) {
sUntilFinished = msUntilFinished / 1000
if (msUntilFinished <= warn1Time) {
}
resultTextViewTimer.text = "${(sUntilFinished / 60)}:${(sUntilFinished % 60).toString().padStart(2, '0')}"
}
override fun onFinish() {
isClockRun = false
}
}
if (!isClockRun) {
if (isEndP) {
isClockRun = true
countDownP.start()
} else {
isClockRun = true
countDown.start()
}
}
}
// **STOP BUTTON**
stopButton.setOnClickListener {
if (isClockRun) {
isClockRun = false
if (isEndP) countDownP.cancel()
if (!isEndP) countDown.cancel()
isEndP = true
}
}
Why don't you make a timer for 70 seconds and do the work of countDownP for 10 seconds and then do the work of countDown for 60 seconds.
In this way, I think it will be more efficient.
I want to create a simple countdown for my game, when the game starts I want this function to be called every second:
fun minusOneSecond(){
if secondsLeft > 0{
secondsLeft -= 1
seconds_thegame.text = secondsLeft.toString()
}
}
I tried this:
var secondsLeft = 15
timer.scheduleAtFixedRate(
object : TimerTask() {
override fun run() {
minusOneSecond()
}
},0, 1000
) // 1000 Millisecond = 1 second
But the app unfortunately stops, the 2nd time the run function is called
I just started with android development and Kotlin 3 weeks ago and so far I understand the most out of it.
With swift in Xcode I use this line and I thought something similar would work with Kotlin
setTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(minusOneSecond), userInfo: nil, repeats: true)
Problem: Timer class uses a background thread with a queue to queue and execute all tasks sequentially. From your code, because you update UI (changing TextView content in minusOneSecond function). That why the app throws the following exception and make your app crash.
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the
original thread that created a view hierarchy can touch its views.
Solution: There are many ways to achieve your task, but I prefer using post() and postDelayed() method from Handler class. Because it's simple and easy to understand.
val mainHandler = Handler(Looper.getMainLooper())
mainHandler.post(object : Runnable {
override fun run() {
minusOneSecond()
mainHandler.postDelayed(this, 1000)
}
})
Update: From author's comment about how to pause/resume the task from Handler. Here is an example.
class MainActivityKt : AppCompatActivity() {
lateinit var mainHandler: Handler
private val updateTextTask = object : Runnable {
override fun run() {
minusOneSecond()
mainHandler.postDelayed(this, 1000)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Your logic code
...
mainHandler = Handler(Looper.getMainLooper())
}
override fun onPause() {
super.onPause()
mainHandler.removeCallbacks(updateTextTask)
}
override fun onResume() {
super.onResume()
mainHandler.post(updateTextTask)
}
fun minusOneSecond() {
if secondsLeft > 0 {
secondsLeft -= 1
seconds_thegame.text = secondsLeft.toString()
}
}
}
I am using this code to update a clock every minute
fixedRateTimer("timer", false, 0L, 60 * 1000) {
this#FullscreenActivity.runOnUiThread {
tvTime.text = SimpleDateFormat("dd MMM - HH:mm", Locale.US).format(Date())
}
}
so you have to run it with paratemer 1000 instead of 60*1000
val timer = object: CountDownTimer(10000, 1000) {
override fun onTick(millisUntilFinished: Long) {
// do something
}
override fun onFinish() {
// do something
}
}
timer.start()
You can also use CountDownTimer for this purpose. As this takes two parameters (the total time and the interval time)
Plus it also provides an on finish method to perform any task when the total time is finished.
please use
inline fun Timer.schedule(
time: Date,
period: Long,
crossinline action: TimerTask.() -> Unit
): TimerTask
reference: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.concurrent/java.util.-timer/schedule.html
I am calling my function every second like this
val handler = Handler()
handler.postDelayed(object : Runnable {
override fun run() {
//Call your function here
handler.postDelayed(this, 1000)//1 sec delay
}
}, 0)
My solution
viewModelScope.launch(Dispatchers.IO) {
while(isActive) {
when(val response = repository.getApi()) {
is NetworkState.Success -> {
getAllData.postValue(response.data)
}
is NetworkState.Error -> this#MainViewModel.isActive = false
}
delay(API_CALL_DELAY)
}
}
if you use any background task or background service try this code
val timer = Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate({
Log.d("RUNNING ","Thread")
},0,10,TimeUnit.SECONDS)
if you work with UI thers like update UI layout try this code
val timer = Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate({
Log.d("RUNNING ","BACKGROUN Thread")
runOnUiThread {
Log.d("RUNNING ","Update UI Thread")
btnUpdate.setText(System.currentTimeMillis().toString())
}
},0,1,TimeUnit.SECONDS)
I'm using recursion with Coroutine its very simple
private fun loop() {
CoroutineScope(IO).launch {
delay(5000)
CoroutineScope(Main).launch {
ManagerToWorker()
loop()
}
}
}
var isActionAchieved = false
var secondsPassed = 0
fun cDTimer(){
if (!isActionAchieved && secondsPassed < 10){ // repeat check if Action NOT Achieved for max of 10 seconds
Handler(Looper.getMainLooper()).postDelayed({
repeatThisFunction()
repeater()
secondsPassed++
}, 1000) //one second till next execution
}
}
fun repeater(){
cDTimer()
}
in the timer I use how to add a time when a button is pressed? for example, I want millisUntilFinished to increase by 5 seconds when I press a button. I tried with the global variable but it didn't.
object :CountDownTimer(10000,1000){
override fun onFinish() {
timeText.text = "Left : 0"
handler.removeCallbacks(runnable)
for (image in imageArray){
image.visibility = View.INVISIBLE
}
for (add in timeAdd){
add.visibility = View.INVISIBLE
}
button.visibility = View.VISIBLE
}
override fun onTick(millisUntilFinished: Long) {
timeText.text = "Left : "+millisUntilFinished/1000
}
}.start()
Here is a count down timer we use
fun message(msg:String){
object : CountDownTimer(4000, 1000) {
override fun onTick(millisUntilFinished: Long) {
tvMsg.visibility = View.VISIBLE
tvMsg.text = msg
}
override fun onFinish() {
tvMsg.visibility = View.INVISIBLE
tvMsg.text = ""
}
}.start()
}
And our use of a plain timer
if (result) {
etItemData.setText("")
message("Record Removed")
Timer().schedule(1000){
thisACTIVITY()
}
Kotlin complains about this not sure why
addition to Vector's answer I made an button that display countdown timer every 1 second. I put Vector's answer into a function then call it when my button is pressed. Hope this help someone. With this example it counts down from 4 seconds.
private fun countdown(){
object : CountDownTimer(4000, 1000) {
override fun onTick(millisUntilFinished: Long) {
otp_resend.text = (millisUntilFinished / 1000).toString()
}
override fun onFinish() {
// do something after countdown is done ie. enable button, change color
etc.
otp_resend.text = "done!"
}
}.start()
}
You can't change the remaining time on an already-created CountDownTimer.
Looking at the source, both millisInFuture and countDownInterval are assigned to final variables; you can't change them.
Now, the mStopTimeInFuture variable, the one the timer actually uses to stop, isn't final, and can be changed. But it's a private variable, meaning you'd need to use reflection, and it might not work properly.
If you want a mutable CountDownTimer, you'll need to roll your own (easiest way would probably be to copy the CountDownTimer source and make the mStopTimeInFuture variable public and add milliseconds to it when needed).
As #TheWanderer answered you can not update the millisUntilFinished as there is no such method available in CountDownTimer class.
To update the Timer you need to stop the current timer and start the new timer with updated millisInFuture value. Here is the sample code which will help you to achieve what you want.
var timer: Timer?=null
//Call this method to start timer on activity start
private fun startTimer(){
timer = Timer(10000);
timer?.start()
}
//Call this method to update the timer
private fun updateTimer(){
if(timer!=null) {
val miliis = timer?.millisUntilFinished + TimeUnit.SECONDS.toMillis(5)
//Here you need to maintain single instance for previous
timer?.cancel()
timer = Timer(miliis);
timer?.start()
}else{
startTimer()
}
}
inner class Timer(miliis:Long) : CountDownTimer(miliis,1000){
var millisUntilFinished:Long = 0
override fun onFinish() {
timeText.text = "Left : 0"
handler.removeCallbacks(runnable)
for (image in imageArray){
image.visibility = View.INVISIBLE
}
for (add in timeAdd){
add.visibility = View.INVISIBLE
}
button.visibility = View.VISIBLE
}
override fun onTick(millisUntilFinished: Long) {
this.millisUntilFinished = millisUntilFinished
timeText.text = "Left : "+millisUntilFinished/1000
}
}