3

When implementing a twitter4j.StatusListner in Kotlin, I get the following IllegalAccessError and associated stack trace:

Exception in thread "main" java.lang.IllegalAccessError: tried to access class twitter4j.StreamListener from class rxkotlin.rxextensions.TwitterExampleKt$observe$1
    at rxkotlin.rxextensions.TwitterExampleKt$observe$1.subscribe(TwitterExample.kt:50)
    at io.reactivex.internal.operators.observable.ObservableCreate.subscribeActual(ObservableCreate.java:40)
    at io.reactivex.Observable.subscribe(Observable.java:10700)
    at io.reactivex.Observable.subscribe(Observable.java:10686)
    at io.reactivex.Observable.subscribe(Observable.java:10615)
    at rxkotlin.rxextensions.TwitterExampleKt.main(TwitterExample.kt:8)

Produced by the following code:

val twitterStream = TwitterStreamFactory().instance
// See https://stackoverflow.com/questions/37672023/how-to-create-an-instance-of-anonymous-interface-in-kotlin/37672334
twitterStream.addListener(object : StatusListener {
    override fun onStatus(status: Status?) {
        if (emitter.isDisposed) {
            twitterStream.shutdown()
        } else {
            emitter.onNext(status)
        }
    }

    override fun onException(e: Exception?) {
        if (emitter.isDisposed) {
            twitterStream.shutdown()
        } else {
            emitter.onError(e)
        }
    }

    // Other overrides.
})
emitter.setCancellable { twitterStream::shutdown }

If I don't use Rx, it makes the exception a bit simpler:

twitterStream.addListener(object: twitter4j.StatusListener {
    override fun onStatus(status: Status) { println("Status: {$status}") }
    override fun onException(ex: Exception) { println("Error callback: $ex") }
    // Other overrides.
})

Exception in thread "main" java.lang.IllegalAccessError: tried to access class twitter4j.StreamListener from class rxkotlin.rxextensions.TwitterExampleKt
at rxkotlin.rxextensions.TwitterExampleKt.main(TwitterExample.kt:14)

However, if I implement a Java wrapper function, no error is thrown and the behaviour is as expected:

Wrapper -

public class Twitter4JHelper {
  public static void addStatusListner(TwitterStream stream, StatusListener listner) {
    stream.addListener(listner);
  }
}

Revised implementation -

val twitterStream = TwitterStreamFactory().instance
val listner = object: StatusListener {
    override fun onStatus(status: Status?) {
        if (emitter.isDisposed) {
            twitterStream.shutdown()
        } else {
            emitter.onNext(status)
        }
    }

    override fun onException(e: Exception?) {
        if (emitter.isDisposed) {
            twitterStream.shutdown()
        } else {
            emitter.onError(e)
        }
    }

    // Other overrides.
}

Twitter4JHelper.addStatusListner(twitterStream, listner)
emitter.setCancellable { twitterStream::shutdown }

This revised solution comes from a blog post, which I think tries to explain the cause but Google translate is not being my friend. What is causing the IllegalAccessError? Is there a purely Kotlin based solution, or will I have to live with this workaround?

junglie85
  • 1,243
  • 10
  • 30

2 Answers2

1

Yep that's not going to work.

addListener method takes a StreamListener param and StreamListener is non-public (package private). I would definitely raise a bug against Kotlin compiler for this.

The code Kotlin compiler generates is:

TwitterStream twitterStream = (new TwitterStreamFactory()).getInstance();
twitterStream.addListener((StreamListener)(new StatusListener() {
         // ..overrides ...
      }));

StatusListener already implements StreamListener so I don't see why the cast is required.

Strelok
  • 50,229
  • 9
  • 102
  • 115
1

I worked around this by using a java utility class:

public class T4JCompat {

  public static void addStatusListener(TwitterStream stream, StatusListener listener) {
    stream.addListener(listener);
  }

  public static void removeStatusListener(TwitterStream stream, StatusListener listener) {
    stream.removeListener(listener);
  }

} 

You can call these methods from Kotlin and things work as expected.

Jeff Jones
  • 176
  • 1
  • 11