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.
Related
I want the text view to be updated every second or less in a dynamic random way and at the end of the loop the text view show the last random number.
I tried to add a sleep method but it did not work every time I click the button the text view show the last random number directly.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val rollButton: Button = findViewById(R.id.button1)
var result: TextView = findViewById(R.id.textView)
rollButton.setOnClickListener {
for (i in 1..10){
result.text = "${(1..6).random()}"
}
Toast.makeText(this, "Dice Rolled!", Toast.LENGTH_SHORT).show()
}
}
}
Coroutine delay is a way to do that, which is suggested in ltp's answer,
Another way is Runnable and Handler, an example function:
fun animateTextView(handler: Handler, textView: TextView, animate: Boolean = true) {
val runnable: Runnable = object : Runnable {
var randomNumber = 0
override fun run() {
randomNumber = (1..100).random()
textView.text = "$randomNumber"
handler.postDelayed(this, 500L)
}
}
// animate == false -> end text updates
if (animate) handler.postDelayed(runnable, 500L)
else handler.removeCallbacks(runnable)
}
Example use of the function:
private val textAnimaterHandler = Handler(Looper.getMainLooper())
animateTextView(textAnimaterHandler, binding.textView)
// Stop updates:
animateTextView(textAnimaterHandler, binding.textView, animate = false)
You can use Coroutine delay
rollButton.setOnClickListener {
CoroutineScope(Dispatchers.Main).launch {
//Disable button temporarily to prevent multiple clicks
it.isEnabled = false
for (i in 1..10) {
binding.textviewFirst.text = "${(1..6).random()}"
//One second delay before the next
delay(1000)
//If you want random delay - say 100ms to 1s
//delay((100L..1000L).random())
}
it.isEnabled = true
Toast.makeText(this#MainActivity, "Dice Rolled!", Toast.LENGTH_SHORT).show()
}
}
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 would like to show a timer counting down in my composable, but I am not sure how to achieve this.
I was thinking to set a delay/timeout for a minute and trigger a recompose that way, but I am not sure if that's the right way to think about it.
#Composable
fun Countdown(completedAt: Date) {
val minutesLeft = ceil((completedAt.time - Date().time) / 60_000.0).toInt()
Handler(Looper.getMainLooper()).postDelayed({
// TODO: Recompose
}, 60_000)
Text(text = "$minutesLeft minutes until completed")
}
My goal is for the text to update every minute with the new time. How can I do this?
Store the amount of minutes as state.
Also make sure to clean up the postDelayed callback inside a DisposableEffect to prevent conflicting delays and memory leaks.
I have moved this logic to a minutesLeft composable function so it can be reused.
#Composable
fun minutesLeft(until: Date): Int {
var value by remember { mutableStateOf(getMinutesLeft(until)) }
DisposableEffect(Unit) {
val handler = Handler(Looper.getMainLooper())
val runnable = {
value = getMinutesLeft(until)
}
handler.postDelayed(runnable, 60_000)
onDispose {
handler.removeCallbacks(runnable)
}
}
return value
}
private fun getMinutesLeft(until: Date): Int {
return ceil((until.time - Date().time) / 60_000.0).toInt()
}
Usage
#Composable
fun Countdown(completedAt: Date) {
val minutes = minutesLeft(until = completedAt)
Text(text = "$minutes minutes until completed")
}
You can use a ViewModel with the CountDownTimer class.
Something like:
val countTimeViewModel : CountTimeViewModel = viewModel()
val minutes = countTimeViewModel.minutes.observeAsState(60)
Button( onClick={
countTimeViewModel.onStartClicked(60000*60) }
){
Text("Start")
}
Text(""+minutes.value)
with:
class CountTimeViewModel : ViewModel() {
private var timer: CountDownTimer? = null
private val _minutes = MutableLiveData(totalTime)
val minutes: LiveData<Int> get() = _minutes
private var totalTime : Long = 0L
fun startCountDown() {
timer = object : CountDownTimer(totalTime, 60000) {
override fun onTick(millisecs: Long) {
// Minutes
val minutes = (millisecs / MSECS_IN_SEC / SECS_IN_MINUTES % SECS_IN_MINUTES).toInt()
_minutes.postValue(minutes)
}
override fun onFinish() {
//...countdown completed
}
}
}
fun onStartClicked(totalTime : Long) {
this.totalTime = totalTime
startCountDown()
timer?.start()
}
override fun onCleared() {
super.onCleared()
timer?.cancel()
}
companion object {
const val SECS_IN_MINUTES = 60
const val MSECS_IN_SEC = 1000
}
}
#Composable
fun minutesLeft(until: Date): Int {
var timeout by remember { mutableStateOf(getMinutesLeft(until)) }
Text(text = "$timeout minutes until completed")
LaunchedEffect(timeout) {
if (timeout > 0) {
delay(1000 * 60)
timeout -= 1
}
}
}
private fun getMinutesLeft(until: Date): Int {
return ceil((until.time - Date().time) / 60_000.0).toInt()
}
these codes may give you an idea to achieve this goal:
val time = (timerDate.time).minus(Calendar.getInstance().timeInMillis)
var timer by remember { mutableStateOf(time) }
LaunchedEffect(key1 = timer) {
if (timer > 0) {
delay(1000L)
timer -= 1000L
}
}
val secMilSec: Long = 1000
val minMilSec = 60 * secMilSec
val hourMilSec = 60 * minMilSec
val dayMilSec = 24 * hourMilSec
val hours = (time % dayMilSec / hourMilSec).toInt()
val minutes = (time % dayMilSec % hourMilSec / minMilSec).toInt()
val seconds = (time % dayMilSec % hourMilSec % minMilSec / secMilSec).toInt()
Text(text = String.format(" %02d:%02d:%02d", hours, minutes, seconds))
How to record the state (click) of two buttons over time. To then reproduce their clicks over time? I think this can be realized through the creation of a .midi file and then its further opening and playback, or can it be done through performClick ()?
Piano-type applications use this feature, where you can record that you played the piano in .midi or .mid format and then play
example of "piano apps"
You can make a ClickRecorder that records button id and delay over a period of time.
data class Click(val id: Int, val timeMs: Long)
class ClickRecorder {
var isRecording = false
private var currentTIme: Long = 0
private val clicks = mutableListOf<Click>()
fun getClicks(): List<Click>? = if (!isRecording) clicks else null
fun recordClick(id: Int) = if (isRecording) {
val diff = System.currentTimeMillis().minus(currentTIme)
clicks.add(Click(id, diff))
currentTIme = System.currentTimeMillis()
} else null
fun start() {
clicks.clear()
currentTIme = System.currentTimeMillis()
isRecording = true
}
fun stop() {
isRecording = false
}
}
Use the ClickRecorder instance in the following way:
val clickRecorder = ClickRecorder()
fun onRecordButtonClick(v: View) {
val btRecord = v as Button
when (btRecord.text) {
RECORD -> {
clickRecorder.start()
btRecord.text = STOP
}
STOP -> {
clickRecorder.stop()
btRecord.text = RECORD
}
}
}
fun onPianoButtonsClick(v: View) {
clickRecorder.recordClick(v.id)
}
After finishing recording you can access recorded clicks by ClickRecorder.getClicks(). To simulate the clicks you need to make ClickPlayer
class ClickPlayer() {
private val index = AtomicInteger(0)
private val handler = Handler()
fun play(list: List<Click>, callback: (Click) -> Unit) {
index.set(0)
val runnable = object : Runnable {
override fun run() {
if (index.get() < list.size) {
val click = list[index.getAndIncrement()]
callback(click)
val nextIndex = index.get()
if (nextIndex < list.size) handler.postDelayed(this, list[nextIndex].timeMs)
}
}
}
handler.postDelayed(runnable, list[index.get()].timeMs)
}
}
Use ClickPlayer in the following way
fun playRecordedClicks(clickPlayer: ClickPlayer, clicks: List<Click>) {
clickPlayer.play(clicks) {
findViewById<Button>(it.id).performClick()
}
}
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()
}