7

I'm trying to implement an Android timer in Kotlin which will fire an event at a defined time interval. I dont want to use TimerTask due to its documented weaknesses (see here) and although there are potentially other ways to do it, I'd like to use a Handler/Runnable in a post-delayed loop. In Java this is possible since the Runnable can refer to itself in the initializer, however in Kotlin it seems this is not possible:

private fun startBoutiqueRefreshTimer(delayMs: Long) {
    val handler = Handler()
    val runnable = Runnable() {
        EventManager.post(BoutiqueRefreshTimerEvent())
        handler.postDelayed(runnable, delayMs)
    }
    handler.postDelayed(runnable, delayMs)
}

because runnable cannot be resolved in the inner postDelayed call. Kotlin apparently prevents variable references from within their own initializers.

What would be a good solution to this problem, still using the Handler/Runnable approach?

Michael Scott
  • 349
  • 1
  • 3
  • 11

4 Answers4

11

This will work

val updateHandler = Handler()

    val runnable = Runnable {
        updateDisplay() // some action(s)
    }

    updateHandler.postDelayed(runnable, 5000).

private fun updateDisplay() {
      Do actions
}

Or if you are not too much familiar with Kotlin, write your code and convert your android Code to Kotlin using Ctrl+Alt+Shift+K".

mx0
  • 6,445
  • 12
  • 49
  • 54
Daya Shankar
  • 111
  • 4
9

You could also use something like this:

private fun startBoutiqueRefreshTimer(delayMs: Long) {
    Handler().apply {
        val runnable = object : Runnable {
            override fun run() {
                EventManager.post(BoutiqueRefreshTimerEvent())
                postDelayed(this, delayMs)
            }
        }
        postDelayed(runnable, delayMs)
    }
}

I used here apply() function to simplify Handler variable, and object expression to allow referencing to Runnable.

As you can see, when I used object expression to initialize Runnable (instead of lambda), then I can use this to reference to Runnable object.

Kamil Kamiński
  • 389
  • 2
  • 12
  • Nice answer. I still have a little trouble still wrapping my head around apply, but this is the answer that comes close to the spirit of the original java implementation. – Michael Scott Sep 01 '17 at 15:08
  • Apply is simple, look at the example I created: https://gist.github.com/Kaminiak/279caea50ee5ce11c908a9a176c279b7 – Kamil Kamiński Sep 01 '17 at 20:40
5

You could move the routine you want to execute periodically in a separate function:

val runnable = Runnable { doJob() }

fun doJob() {
    EventManager.post(BoutiqueRefreshTimerEvent())
    handler.postDelayed(runnable, delayMs)
}

Note that the variable runnable needs to be accessable in scope of the doJob()-function, that means it should be a class-member, if you are in a class/object.

Piwo
  • 1,347
  • 1
  • 12
  • 19
  • Yes this works. A little messier than the java version because it requires some additional members (and I'd like to have multiple of these timer events being fired for different delay intervals), but this is acceptable. – Michael Scott Sep 01 '17 at 14:11
0

This worked for me, where Hanlder using postDelayed

    override fun onCreateView(inflater: LayoutInflater?, container: 
    ViewGroup?, savedInstanceState: Bundle?): View? {
    val handler = Handler()
    handler.postDelayed(mSplashHandler,1500)
    return view
    }

   internal var mSplashHandler: Runnable = Runnable {
   // your code to do after handler completes
   }
Divya
  • 121
  • 2
  • 11