Kotlin: llamar a una función cada segundo

4 minutos de lectura

Quiero crear una cuenta regresiva simple para mi juego, cuando el juego comience, quiero que se llame a esta función cada segundo:

fun minusOneSecond(){
  if secondsLeft > 0{
     secondsLeft -= 1
     seconds_thegame.text = secondsLeft.toString()
  }
}

Intenté esto:

var secondsLeft = 15

timer.scheduleAtFixedRate(
   object : TimerTask() {

      override fun run() {
         minusOneSecond()
      }

    },0, 1000
)   // 1000 Millisecond  = 1 second

Pero, lamentablemente, la aplicación se detiene, la segunda vez que se llama a la función de ejecución

Empecé con el desarrollo de Android y Kotlin hace 3 semanas y hasta ahora lo entiendo al máximo.

Con swift en Xcode uso esta línea y pensé que algo similar funcionaría con Kotlin

setTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(minusOneSecond), userInfo: nil, repeats: true)

  • Posible duplicado de ¿Cómo llamar a una función después de un retraso en Kotlin?

    – no iniciar sesión

    8 de abril de 2019 a las 10:14

  • RunOnUiThread es necesario porque la interfaz de usuario solo se puede manipular desde un subproceso de interfaz de usuario y la devolución de llamada ocurre en un subproceso de fondo temporal.

    – Eugenio Petrenko

    08/04/2019 a las 10:15

  • ¿Qué hay de usar un AsyncTask ? Con una tarea en segundo plano compuesta por un bucle, que espera un segundo y luego actualiza el progreso. Y un método onProgressUpdate() que actualiza su interfaz de usuario.

    – vincrichaud

    8 de abril de 2019 a las 10:17

Avatar de usuario de Son Truong
hijo truong

Problema: Temporizador class usa un subproceso de fondo con una cola para poner en cola y ejecutar todas las tareas secuencialmente. Desde su código, porque actualiza la interfaz de usuario (cambiando el contenido de TextView en minusOneSecond función). Es por eso que la aplicación lanza la siguiente excepción y hace que su aplicación se bloquee.

android.view.ViewRootImpl$CalledFromWrongThreadException: solo el subproceso original que creó una jerarquía de vistas puede tocar sus vistas.

Solución: Hay muchas maneras de lograr su tarea, pero prefiero usar correo() y postretrasado() método de Manipulador clase. Porque es simple y fácil de entender.

val mainHandler = Handler(Looper.getMainLooper())

mainHandler.post(object : Runnable {
    override fun run() {
        minusOneSecond()
        mainHandler.postDelayed(this, 1000)
    }
})

Actualizar: Del comentario del autor sobre cómo pausar/reanudar la tarea de Handler. Aquí hay un ejemplo.

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()
        }
    }
}

Estoy usando este código para actualizar un reloj cada minuto.

 fixedRateTimer("timer", false, 0L, 60 * 1000) {
     this@FullscreenActivity.runOnUiThread {
         tvTime.text = SimpleDateFormat("dd MMM - HH:mm", Locale.US).format(Date())
     }
 }

así que tienes que ejecutarlo con paratemer 1000 en vez de 60*1000

val timer = object: CountDownTimer(10000, 1000) {
    override fun onTick(millisUntilFinished: Long) {
        // do something
    }
    override fun onFinish() {
        // do something
    }
}
timer.start()

También puede usar CountDownTimer para este propósito. Como esto toma dos parámetros (el tiempo total y el intervalo de tiempo)

Además, también proporciona un método de finalización para realizar cualquier tarea cuando finaliza el tiempo total.

  • Esto no está bien. Te limita a tener que saber la cantidad máxima de tiempo por adelantado y terminará cuando se alcance ese tiempo.

    – Juan

    29 de abril de 2021 a las 16:08

Avatar de usuario de Sandy Genedy
sandy genedy

por favor use

inline fun Timer.schedule(
    time: Date, 
    period: Long, 
    crossinline action: TimerTask.() -> Unit
): TimerTask

referencia: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.concurrent/java.util.-timer/schedule.html

Estoy llamando a mi función cada segundo así

val handler = Handler()
  handler.postDelayed(object : Runnable {
      override fun run() {
            //Call your function here
            handler.postDelayed(this, 1000)//1 sec delay
        }
}, 0)

  • ¿Por qué poner 0 allí?

    – Arbaz.in

    7 de octubre de 2021 a las 4:40

Avatar de usuario de Thiago
Thiago

Mi solución

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)
            }
        }

  • ¿Por qué poner 0 allí?

    – Arbaz.in

    7 de octubre de 2021 a las 4:40

Avatar de usuario de Hussain Tarek
hussain tarek

Estoy usando recursividad con Coroutine es muy simple

  private fun loop() {
    CoroutineScope(IO).launch {
        delay(5000)
        CoroutineScope(Main).launch {
            ManagerToWorker()
            loop()
        }
    }
}

  • ¿Eso no haría crecer la pila hasta el desbordamiento?

    – Antzi

    6 de agosto de 2020 a las 7:57

  • @Antzi se desbordará, pero la pila es lo suficientemente grande como para que el comentarista no pueda verla con 5 segundos de retraso.

    –Dominik Murzynowski

    7 de julio de 2021 a las 7:32

  • La recursividad a menudo conduce a desbordamientos de pila inesperados. Si bien esta solución puede “funcionar” en situaciones limitadas, eventualmente fallará (quizás después de muchas horas o incluso días dado el retraso de 5000 ms).

    – Mate

    28 de marzo a las 15:45

  • Mientras pienso más en esto, es posible que técnicamente no cause un desbordamiento de la pila, ya que cada llamada a CoroutineScope(Main).launch crearía un ejecutable separado. Sin embargo, aún conduciría a una pérdida de memoria en la mayoría de los casos, ya que Stacktrace Recovery recordaría cada llamada. (Si ManagerToWorker() lanzó una excepción, ¡el seguimiento de la pila podría ser enorme!) github.com/Kotlin/kotlinx.coroutines/blob/master/docs/topics/…

    – Mate

    15 abr a las 16:21

¿Ha sido útil esta solución?