I am new to Android Development... Sorry for asking something so trivial. I don't know how to google this problem.
So let me explain:
I am doing the Android Development Course from Google and in the exercise you have a TextView that is repeatedly changed by an easy mathematical function 4 times after setContentViewis called. Between the changes Thread.sleep(1000) is called to delay the changes.
Expected behavior:
The Main Activity starts and you can see the TextView changing 4 times.
What actually happens:
The App start is delayed for as long as the calculations are set and then afterwards it will display the Main Activity with only the very last calculated result. In this case I would wait 4 seconds (with Thread.sleep(1000) being called 4 times) until the App is completely up and then you only see the result 60 because of 60 / 1.
This is the code:
private const val TAG = "MainActivity"
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
division()
}
private fun division() {
val numerator = 60
var denominator = 4
repeat(4) {
Thread.sleep(1000)
Log.v(TAG, "${numerator / denominator}")
val view: TextView = findViewById(R.id.division_textview)
view.setText("${numerator / denominator}")
denominator--
}
}
Thanks in advance. I hope someone knows why Google is suggesting this code, but it does not work on my machine.
You need to make a delay by Timer instead of Thread.sleep()
For training you can try something like this.
private val timerHandler = Handler(Looper.getMainLooper())
private val timerRunnable = Runnable {
denominator--
if(demominator != 0) {
//UI changes here
decrementTimer()
}
}
private fun decrementTimer() {
timerHandler.postDelayed(timerRunnable, DELAY_TIME)
}
If you need to start first run immediately use timerRunnable.run() in other case call decrementTimer().
Also would be helpful to control handler with removeCallbacks function when activity goes to background
You can wait in another thread so you don't block the main thread,
try this code it should work correctly, the code inside the lifecycleScope.launch will be moved to another thread so it will not block the UI:
private const val TAG = "MainActivity"
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
division()
}
private fun division() {
val numerator = 60
var denominator = 4
lifecycleScope.launch {
repeat(4) {
delay(1000)
Log.v(TAG, "${numerator / denominator}")
val view: TextView = findViewById(R.id.division_textview)
view.setText("${numerator / denominator}")
denominator--
}
}
}
Note: You need to be sure that you add the lifecycle dependency in you app gradle
dependencies {
...
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
...
}
Thanks for your answers everyone. Each of them helped me searching for the right topics. I liked the Handler Class the most and after some further searching, I came up with this solution:
private fun division() {
val numerator = 60
var denominator = 4
val handler = Handler(Looper.getMainLooper())
val divRunnable = object: Runnable {
override fun run() {
if (denominator != 0) {
Log.v(TAG, "${numerator / denominator}")
val view: TextView = findViewById(R.id.division_textview)
view.text = "${numerator / denominator}"
denominator--
handler.postDelayed(this, 1000)
}
}
}
handler.post(divRunnable)
}
This is working exactly as I wanted it to work.
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 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()
}
I want to record sensor value in every 5ms
But I don't know whether the values are correct if I capture values in other thread. e.g. x is the last value while y and z are previous value
So I write this program to test, if value is wrong, Log.v("abc not equal", "a:$a b:$b c:$c") will be called
But the result is no problem, a b c are always equal
Should I trust this result? Why a b c never be different? (e.g. a:10 b:9 c:9), is the Hander waits SensorEventListener finish?
class MainActivity : AppCompatActivity() {
var a=0
var b=0
var c=0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
sensorManager.registerListener(sensorListener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
val handler = Handler()
val run = object : Runnable{
override fun run() {
if(!(a == b && b == c)){
Log.v("abc not equal", "a:$a b:$b c:$c")
}
handler.postDelayed(this,5)
}
}
handler.postDelayed(run,5)
}
private val sensorListener = object: SensorEventListener {
override fun onAccuracyChanged(p0: Sensor?, p1: Int) {}
override fun onSensorChanged(event: SensorEvent?) {
if(event != null){
val xRaw = event.values[0]
val yRaw = event.values[1]
val zRaw = event.values[2]
if(a>100){
a=0
b=0
c=0
}
a+=1
slow()
b+=1
slow()
c+=1
}
}
}
fun slow(){
var x = 0
while (x < 50000) {
x++
if(x>10) {
var y = Random().nextInt(x - 1)
}
}
}
}
"registerListener()" method has an additional parameter that defines the Handler to be used. You'r not using that additional parameter so the Listener will run all its callback methods ("onSensorChanged()", etc..) in the MainThread/UiThread. Even your "handler" (and the Runnable object) variable is running in the Main/UiThread, so there isn't any problem. Problems could happen if your "registerListener()" method OR/AND the "handler" variable use different Threads.
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
}
}
I'm just starting with Kotlin to make Android Apps (it's my first programming language) and I'm using a CountDownTimer. I want to access the p0 parameter globally, but I don't know how to. Here's the code:
class MainActivity : AppCompatActivity() {
var score: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var timeTimer = object : CountDownTimer(10000, 1000) {
override fun onFinish() {
timeView.text = "Time's up!"
}
override fun onTick(p0: Long) {
timeView.text = "Time's left: " + p0/1000
}
}.start()
}
fun point (view: View) {
score++
p0 = p0 + 1000
scoreView.text = "Score: $score"
}
}
Function point is called by clicking a button. The thing I'm trying to achieve is making CountDownTimer longer by 1 second every time a user clicks a button.
This line p0 = p0 + 1000 obviously won't work because p0 is inside another code block. Is there any way to make it globally accessible? I've thought about putting CountDownTimer outside onCreate and just starting it in the onCreate, but I think it still wouldn't work as p0 is still inside CountDownTimer code block.
var timeTimer = object : CountDownTimer(10000, 1000) {
override fun onFinish() {
timeView.text = "Time's up!"
}
override fun onTick(p0: Long) {
timeLeft = p0 //timeLeft is global --only way I think to keep track remaining time.
timeView.text = "Time's left: " + p0/1000
}
}.start()
This should be global (better at the bottom after all methods of the Activity)
fun resetTimer(time){
timeTimer.cancel()
timeTimer = object : CountDownTimer(time, 1000) {
override fun onFinish() {
timeView.text = "Time's up!"
}
override fun onTick(p0: Long) {
timeLeft = p0
timeView.text = "Time's left: " + p0/1000
}
}.start()
}
So finally
fun point (view: View) {
score++
resetTimer(timeLeft + 1000)
scoreView.text = "Score: $score"
}
I don't know what's the reach of your App, but you can do a couple of things:
Create a custom Application class for your App and check the variable there. You need to create a class that extends from Application and add that class into your manifest, in the <application/> tag under the android:name attribute. You can access your custom Application class from anywhere by using it like a Singleton, for example MyApp.getInstance().setPvalue(someIntValue).
Maybe you want to save that value in your SharedPreferences so It's kept saved if you close the App.
Maybe you want to have that value in a Service, so if you close the App the timer is still going to be counting down.
I answer a pretty similar question the other day:
How to keep the countDownTimer counts after i swap the activity?
Hope it helps.