Result of obj.apply{ func1() } is different than obj.func1()

Viewed 69

In kotlin, apply{} is defined as inline fun <T> T.apply(block: T.() -> Unit): T
I thought the use of this function is to minimize this code

obj.func1()
obj.func2()
obj.func3()
...

to

obj.apply {
    func1()
    func2()
    func3()
}

But now I think it has some totally different actual use case. In my android studio project, i got this log

2020-12-08 13:3:22.648 28006-28006/com.skb.skara D/VideoViewActivity: onStart: pos 2677
2020-12-08 13:3:22.649 28006-28006/com.skb.skara D/VideoViewActivity: initializePlayer: seeked to 0
2020-12-08 13:3:22.650 28006-28006/com.skb.skara D/VideoViewActivity: onStart: pos 2677

for this code

private var currentPosition: Int

override fun onStart() {
    super.onStart()
    Log.d(TAG, "onStart: pos $currentPosition")
    initializePlayer()
    Log.d(TAG, "onStart: pos $currentPosition")
    if (currentPosition == 0) {
        Log.d(TAG, "onStart: vid start")
        fullscreen_video.start()
    }
    show()
}

private fun initializePlayer() {
    fullscreen_video.apply {
        setVideoPath(videoUrl)
        seekTo(currentPosition)
        Log.d(TAG, "initializePlayer: seeked to $currentPosition")
    }
}

Then I changed initialisePlayer function to this

private fun initializePlayer() {
    fullscreen_video.setVideoPath(videoUrl)
    fullscreen_video.seekTo(currentPosition)
    Log.d(TAG, "initializePlayer: seeked to $currentPosition")
}

and then I got this log

2020-12-08 13:12:53.548 28006-28006/com.skb.skara D/VideoViewActivity: onStart: 2677
2020-12-08 13:12:53.550 28006-28006/com.skb.skara D/VideoViewActivity: initializePlayer: seeked to 2677
2020-12-08 13:12:53.550 28006-28006/com.skb.skara D/VideoViewActivity: onStart: 2677

Someone please explain this behaviour and also explain the use of scope function apply{}

2 Answers

Be careful, VideoView has its own currentPosition. Accessing currentPosition inside its apply block will get the currentPosition of the VideoView.

private fun initializePlayer() {
    fullscreen_video.apply {
        setVideoPath(videoUrl)
        seekTo(currentPosition) // This will use VideoView.currentPosition
        Log.d(TAG, "initializePlayer: seeked to $currentPosition") // This, too
    }
}

Explain about apply:

The context object is available as a receiver (this). And this always can be omitted.

If you want to access the currentPosition you declared above, use this@YourActivity.currentPosition. Replace YourActivity with your activity.

See the table below, within the scope of also, this.currentPosition is fullscreen_video.currentPosition, instead of your var currentPosition

Kotlin's scope operators are like a swiss army knife with a method for every scenario.

https://kotlinlang.org/docs/reference/scope-functions.html

Function Object reference Return value Is extension function
let it Lambda result Yes
run this Lambda result Yes
run - Lambda result No: called without the context object
with this Lambda result No: takes the context object as an argument.
apply this Context object Yes
also it Context object Yes

It can be really useful for DSL APIs and also calling multiple setters after creating a simple Java object.

    val x = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply {
      init(null as KeyStore?)
    }

In this form the incomplete object is never available until fully constructed. So you can assign directly to a field (ignoring concurrency and memory barriers).

But also keeps things in expression form, so you can chain operators instead of having values you mutate imperitively.

Here is an example where also is useful instead.

fun getRandomInt(): Int {
    return Random.nextInt(100).also {
        writeToLog("getRandomInt() generated value $it")
    }
}

If you switch to also, it should make this difference clearer for your case.

Related