5

TL;DR These object : someClass{ } anonymous objects can't access itself via this (which results the outer object). How can I access it?

Longer explanation:

For my Fragment I need a PreDrawListener. I call this in onCreateView. When executing, I wanted to remove the listener afterwards. So the Java way of doing would suggest something like this

  override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,    

  val treeObserver = layout.viewTreeObserver

  treeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
     override fun onPreDraw(): Boolean {
        layout.viewTreeObserver.removeOnPreDrawListener(this)
        ...
     }
  }

The problem is, when taking a look at the removeOnPreDrawListener(this) the this object is not the listener but myFragment$onCreateView$1@f019bf0

Alternatively I could access this@MyFragment which returns the reference to the Fragment directly.

Still, none of these options seems to be my PreDrawListener. How can I access it from inside (if at all)?

Tobias Reich
  • 4,952
  • 3
  • 47
  • 90
  • 2
    This definitely works in Kotlin, I've just tried it. Also, [here's](https://github.com/android/android-ktx/blob/89ee2e1cde1e1b0226ed944b9abd55cee0f9b9d4/src/main/java/androidx/core/view/View.kt#L75) this exact thing implemented in the android-ktx library, which also works. Maybe there's a problem with your setup, or you're somehow in the wrong scope? Could you post a minimal verifiable example? – zsmb13 Sep 07 '18 at 18:01
  • 1
    `myFragment$onCreateView$1@f019bf0` is the listener's `toString()` inherited implementation. You should be able to override it (in addition to `onPreDraw`) and see it change. – Alexey Romanov Sep 07 '18 at 22:27

1 Answers1

3

I honestly don't see your problem.

this inside an anonymous refers to the class itself, but they never have names. You can't create an anonymous class with a name. To demo this, I wrote some sample code:

class TheClass{
    fun run(){
        val anon = object: Runnable {
            override fun run() {}
        }
        println(anon::class.java.simpleName)
        println(anon::class.java.name)
    }
}

Which prints:

run$anon$1
com.package.TheClass$run$anon$1

Now, this is nice and all, but it still doesn't look like yours. But you see it matches the containing class, method, variable, and finally the dollar sign denoting that it's an anonymous inner class. That applies to the second, which is the full one. The first just prints the short name, which is the method, var name, and again the dollar sign that shows it's anonymous function.

If you're interested in why the dollar sign with a number appears, see this. T

Let's expand that and ditch the variable. Obviously, this is horrible code (and far from memory-efficient, but it's a demo so it doesn't matter):

class TheClass {
    fun run(){
        println(object: Runnable {
            override fun run() { }
        })
    }
}

This prints, and matching your pattern:

com.package.TheClass$run$anon$1

You've seen the pattern; now you can start "decoding" the hash you got:

myFragment // inside myFragment
$onCreateView // Inside a function
$1 // There is an anonymous class with a specific identifier
@f019bf0 // This is standard everywhere; just look up Object.toString()

What I just tried to prove is: this does refer to the anonymous function you create. Anonymous functions are, well, anonymous. They don't have names. They use $number as identifiers. So if you have this code:

treeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
    override fun onPreDraw(): Boolean {
       layout.viewTreeObserver.removeOnPreDrawListener(this)
       ...
    }
 }

this will refer to the listener, even though printing the class may print stuff that looks confusing. If there's something that's broken, it's not because of this not referring to the listener (because it does)

Also, your code compiles fine. There's no type mismatch in there either. If it referred to a different object, it wouldn't work if you passed this to a method that requires a OnPreDrawListener.


You would get a different result with the same code in Java. This is because Kotlin compiles anonymous functions as Class$function$number, where as Java compiles it to Class$number. If it's in a nested class, it will appear as Outer$Inner$function$number in Kotlin, and Outer$Inner$number in Java.

It's a difference in the compiler that results in different names; Java excludes the function, where as Kotlin includes it. It's in the .class filename, so if you build your project and look at the file names in a file explorer for whatever OS you have (Do not look in IntelliJ. It will decompile the files for you. And remember, you're just looking for the name, which IntelliJ messes up by merging the .class files into a single one to match the original source)


Just as final meta, I do print the class instead of printing the object. Interfaces do not have an overridden toString method, which means it defaults to the one on Object, which returns getClass().getName() + "@" + Integer.toHexString(hashCode()); (original code can be found here). println(this) is the same as println(this.toString()), which calls the toString method in Object, which prints the class name. println(this) is the same as printing the object or printing the class

Zoe
  • 27,060
  • 21
  • 118
  • 148
  • Wow, goog explaination. So I simply misinterpreted the naming. Thanks a lot! – Tobias Reich Sep 09 '18 at 08:09
  • 1
    This doesn't explain how to refer to the anonymous object when there is a naming conflict between the object and the containing class, like a member and a local variable with the same name. For that you need something like this@???, but it's not clear what to use. – EntangledLoops Feb 17 '20 at 05:11
  • @Zoe Cool explanation. I have a similar issue that I've posted here (https://discuss.kotlinlang.org/t/how-to-reference-an-anonymous-inner-class-from-the-outer-class/18784). Could you take a look and provide insight into it? – Saifur Rahman Mohsin Aug 24 '20 at 08:19