Gieted
Gieted

Reputation: 895

error: [Dagger/MissingBinding] error message doesn't make sense

I'm getting:

C:\Users\Gieted\Projekty\CraftIt\runtime\build\tmp\kapt3\stubs\main\org\craftit\runtime\MainComponent.java:8: error: [Dagger/MissingBinding] java.lang.ClassLoader cannot be provided without an @Provides-annotated method.
public abstract interface MainComponent {
                ^
  A binding with matching key exists in component: org.craftit.runtime.server.ServerComponent
      java.lang.ClassLoader is injected at
          org.craftit.runtime.resources.packets.converters.DisplayMessageConverter(�, classLoader, �)
      org.craftit.runtime.resources.packets.converters.DisplayMessageConverter is injected at
          org.craftit.runtime.resources.packets.converters.PacketConverter(�, displayMessageConverter)
      org.craftit.runtime.resources.packets.converters.PacketConverter is injected at
          org.craftit.runtime.Bridge(�, packetConverter, �)
      org.craftit.runtime.Bridge is injected at
          org.craftit.runtime.server.initializers.VanillaServerInitializer(bridge, �)
      org.craftit.runtime.server.initializers.VanillaServerInitializer is injected at
          org.craftit.runtime.server.ServerModule.nativeServerInitializer(to)
      org.craftit.runtime.server.initializers.NativeServerInitializer is requested at
          org.craftit.runtime.server.ServerComponent.nativeServerInitializer() [org.craftit.runtime.MainComponent ? org.craftit.runtime.server.ServerComponent]
  It is also requested at:
      org.craftit.runtime.resources.entities.player.ChatType(�, classLoader)
      org.craftit.runtime.resources.entities.player.NativeConnectorImpl(�, classLoader)
      org.craftit.runtime.resources.packets.converters.SendChatMessageConverter(�, classLoader)
      org.craftit.runtime.text.NativeColorFactory(�, classLoader)
      org.craftit.runtime.text.StringTextComponentFactory(�, classLoader, �)
      org.craftit.runtime.text.StyleFactory(�, classLoader, �)
  The following other entry points also depend on it:
      org.craftit.runtime.server.ServerComponent.entityRegistry() [org.craftit.runtime.MainComponent ? org.craftit.runtime.server.ServerComponent]

So "A binding with matching key exists in component: org.craftit.runtime.server.ServerComponent" suggests that the requested binding is found in ServerComponent, but at the same time is requested at org.craftit.runtime.server.ServerComponent.nativeServerInitializer() tells that the missing binding comes from ServerComponent, which doesn't make any sense. Can someone explain to me what's going on, and where should I search for errors?

@ServerScope
@Subcomponent(modules = [ServerModule::class])
interface ServerComponent {

    fun pluginLoader(): PluginLoader

    fun entityRegistry(): EntityRegistry

    fun nativeServerInitializer(): NativeServerInitializer

    fun commandRegistry(): CommandRegistry

    fun pluginRegistry(): PluginRegistry

    @Subcomponent.Factory
    interface Factory {
        fun create(@BindsInstance server: Server): ServerComponent
    }
}
@Singleton
@Component(modules = [MainModule::class])
interface MainComponent {

    @Named("new")
    fun server(): Server

    @Component.Factory
    interface Factory {
        fun create(@BindsInstance configuration: Configuration): MainComponent
    }
}
class VanillaServer @Inject constructor(
    serverComponentFactory: ServerComponent.Factory
) : Server {

    override val entities: EntityRegistry
    override val commands: CommandRegistry
    override val plugins: PluginRegistry

    private val pluginLoader: PluginLoader

    private val nativeServerInitializer: NativeServerInitializer

    init {
        val component = serverComponentFactory.create(this)
        pluginLoader = component.pluginLoader()
        entities = component.entityRegistry()
        nativeServerInitializer = component.nativeServerInitializer()
        commands = component.commandRegistry()
        plugins = component.pluginRegistry()
    }

    override fun start() {
        nativeServerInitializer.startServer()
        pluginLoader.loadPlugins()
        plugins.forEach { it.enable() }
    }
}
@Module
abstract class ServerModule {
    companion object {
        @Provides
        @ServerScope
        fun classLoader(configuration: Configuration): ClassLoader =
            URLClassLoader(arrayOf(configuration.serverFile.toURI().toURL()))
    }

    @Binds
    @ServerScope
    abstract fun entityRegistry(to: VanillaEntityRegistry): EntityRegistry

    @Binds
    @ServerScope
    abstract fun nativeServerInitializer(to: VanillaServerInitializer): NativeServerInitializer
}

Upvotes: 2

Views: 1150

Answers (1)

Jeff Bowman
Jeff Bowman

Reputation: 95704

The real problem is far on the right side of your first line:

MainComponent.java:8: error: [Dagger/MissingBinding] java.lang.ClassLoader cannot be provided without an @Provides-annotated method.

You can see it provided in ServerModule, but you're getting the message because something that is supposedly accessible from the MainComponent could not get to the ClassLoader you provide in the ServerComponent. This makes sense: Bindings from MainComponent are accessible in ServerComponent, but bindings in ServerComponent are not accessible in MainComponent unless retrieved through a specific instance of ServerComponent that you create through ServerComponent's factory.

This is also why your solution worked:

VanillaServerInitializer and Bridge are server scoped, PacketConverter and DisplayMessageConverter were unscoped. If I try adding @ServerScope to PacketConverter I'm getting: error: [Dagger/IncompatiblyScopedBindings] org.craftit.runtime.MainComponent scoped with @Singleton may not reference bindings with different scopes. This is weird, because PacketConverter is never created by MainComponent.

Setting either singleton or server scope on every binding fixed the issue. But why have it happened in the first place?

Since you'd had PacketConverter and DisplayMessageConverter unscoped, Dagger tried to implement them in your MainComponent where a ClassLoader could not be supplied. (I also would have expected PacketConverter to be implemented in ServerComponent if at all possible, but what you're describing would be more consistent with it being accessed somewhere transitively from MainComponent's bindings, either through @Named("new") Server or a binding in the MainModule you've omitted.)

In any case, as long as you only try to access your bindings from @ServerScoped and unscoped classes, never from @Singleton or MainComponent-accessible classes, you should be fine.

Upvotes: 3

Related Questions