Reputation: 3388
I have two packages:
1. com.test.hidden
:
Contains an protected interface MyListener
and a public class FancyClass
with a public method for registering MyListener
objects:
package com.test.hidden;
public class FancyClass {
public void registerListener(MyListener listener) {
// ...
}
}
package com.test.hidden;
interface MyListener {
void doSomething();
}
2. com.test.myapp
:
Here I have an instance of FancyClass
and I want to register a new MyListener
object to it.
Obviously this will not work since MyListener
is not visible in com.test.myapp
. My intuition was to create a new interface in com.test.hidden
that extends MyListener
and is public:
package com.test.hidden;
public interface MyListenerWrapper extends MyListener {
@Override
void doSomething();
}
package com.test.myapp
import com.test.hidden.FancyClass
import com.test.hidden.MyListenerWrapper
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val fc = FancyClass()
fc.registerListener(object : MyListenerWrapper {
override fun doSomething() {
// ...
}
})
}
}
But I get an IllegalAccessError
:
java.lang.IllegalAccessError: Illegal class access: 'com.test.myapp.MainActivity' attempting to access 'com.test.hidden.MyListener' (declaration of 'com.test.myapp.MainActivity' appears in /data/app/com.test.myapp-ZzNlHhuBCCdAEV4QsZHspw==/base.apk)
at com.test.myapp.MainActivity.onCreate(MainActivity.kt:15)
at android.app.Activity.performCreate(Activity.java:7136)
at android.app.Activity.performCreate(Activity.java:7127)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1272)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2899)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3054)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1814)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:280)
at android.app.ActivityThread.main(ActivityThread.java:6710)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
When I change the visibility of MyListener
to public it works just fine, so I suspect Java does not like increasing the visibility of interfaces in subinterfaces. Why doesn't this work and how to solve this?
Upvotes: 1
Views: 319
Reputation: 28298
Kotlin does not like exposing internal API's in public methods, and actually go as far as giving compiler errors if you do this. This also affects Java code if you call the code from Kotlin, but it throws an exception at runtime rather than at compile time.
As for solutions, Kotlin has internal
, but this is a really special keyword: unlike Java's package private (no modifier), internal makes it visible everywhere in the module.
That's also one solution: convert to Kotlin and use internal
. If you're making a library, it won't be visible outside the module, but you'll be able to declare it internally at least.
The second is converting the interface to a class and using a sealed class
. Note that this requires all children to be declared in the same file. See this for more info on sealed classes.
And the final solution: make it public. I highly recommend you go with this over the other alternatives, mainly because it's the easiest option and the one that doesn't require hackish design. Also, if you're planning on accessing the interface somewhere else, you'd have to use specific implementations rather than the general superclass, which isn't always feasible (depends on your use though).
And there's also the option of only using Java, but I assume you'd prefer mixing since you're already doing that. But as Michael mentioned, you should really avoid this kind of design: it's bad design. If I were you, I'd make it public either way.
Upvotes: 1