15

I'm trying Kotlin and want to implement a lazy extension property for Activity:

/**
 * Activity module
 */
val Activity.activityModule: ActivityModule by lazy {
    ActivityModule(this)
}

The compiler errors with:

'this' is not defined in this context

How can I qualify this as Activity this? I have read a guide but can't get it. this@Activity says the reference is unresolved.

Konrad Jamrozik
  • 3,254
  • 5
  • 29
  • 59
Motorro
  • 227
  • 2
  • 10
  • Can you show more code? Class declaration with this property? – Eugene Krivenja Dec 21 '15 at 09:42
  • @EugeneKrivenja, that is a top-level property. Intended to use it in Activity subclass methods to create dagger module. So it is defined in a separate file. Something like [that](https://github.com/Kotlin/anko/blob/a9109b510d362dbf133aa68d793fb1ebf1fdbd7b/dsl/static/src/common/Intents.kt) – Motorro Dec 21 '15 at 10:16
  • See also https://youtrack.jetbrains.com/issue/KT-13053 with one possible workaround – Vadzim Jul 27 '16 at 14:38

4 Answers4

10

Other answers here have pointed out that it is impossible to reference this within the stdlib's current implementation of the lazy receiver, and that one could implement their own delegate. So I decided to implement it and post it here...:

class LazyWithReceiver<This,Return>(val initializer:This.()->Return)
{
    private val values = WeakHashMap<This,Return>()

    @Suppress("UNCHECKED_CAST")
    operator fun getValue(thisRef:Any,property:KProperty<*>):Return = synchronized(values)
    {
        thisRef as This
        return values.getOrPut(thisRef) {thisRef.initializer()}
    }
}

Here is some code showing how to use it.

This implementation uses a weak hash map to store a separate value for each receiver...this comes with a couple of implications...:

  • distinct instances that are structurally equal will share the same value.

  • in some cases, values that have already been initialized for some receiver could be garbage collected which means that the initializer might be called again to re-initialize the value if it is accessed again.

Community
  • 1
  • 1
Eric
  • 16,397
  • 8
  • 68
  • 76
4

The lazy delegate in Kotlin doesn't have reference to a property member class.

I see two solutions:

  1. transform it to extension function
  2. implement own delegate
Eugene Krivenja
  • 647
  • 8
  • 18
4

lazy calls initializer function when it is accessed first time and then stores the value returned by the initializer to return that value on successive accesses.

An instance of Lazy is capable of storing exactly one value. When you delegate extension property to a Lazy instance, you're getting a single instance of Lazy serving getValue requests from all instances of the receiver type, in your case it's Activity. This results in Lazy computing value only for first Activity and using that value on all subsequent calls for other instances of Activity.

Therefore while it's syntactically possible to pass an Activity to initializer as a receiver and refer it as this inside as @voddan suggests in this answer, the Lazy itself is not capable of storing different value for different receivers.

An ability to have an external storage for extension properties may likely be covered by "Attached properties" feature KT-7210. I don't think Lazy should have this ability as it complicates significantly its implementation.

Ilya
  • 21,871
  • 8
  • 73
  • 92
  • Indeed. Didn't get it the right way. As soon as top-level is resolved statically - the only one instance will be created – Motorro Dec 21 '15 at 19:11
1

I think there is no way to access the Activity from the body of lazy, at least with the current signature\implementation: fun <T> lazy(initializer: () -> T): Lazy<T>

To do that the signature would have to look like

fun <A, T> lazy(initializer: A.() -> T): Lazy2<A, T>

You can implement such an extended function yourself, or\and report this as an issue with stdlib

voddan
  • 31,956
  • 8
  • 77
  • 87
  • What instance of type `T` would you expect should be passed as a receiver to initializer? – Ilya Dec 21 '15 at 15:21
  • @Ilya I think `thisRef as T` should be the receiver: https://kotlinlang.org/docs/reference/delegated-properties.html – voddan Dec 21 '15 at 15:54
  • 1
    So this lazy implementation would be limitied to returing values of the same type as the receiver? – Ilya Dec 21 '15 at 16:00
  • Thanks guys! Anyway that does not do the trick with a top-level extension property as there is no 'this' value and 'thisRef' when defining initializer. So you come to the same result. Ended up with extension function. – Motorro Dec 21 '15 at 19:15
  • @Motorro of course there is, the stdlib just needs to be rewritten. I reported your problem to the kotlin team – voddan Dec 22 '15 at 00:07
  • @voddan could you please share a link to report? – Motorro Dec 22 '15 at 03:06
  • 1
    @voddan I've created a gist [here](https://gist.github.com/motorro/e650bfb3942728226bb5) with a basic implementation. It'll be great if you could comment it and point out how would you create a top-level property with initializer above. – Motorro Dec 22 '15 at 03:15