Skip to content

@Scope(name=...) + @Scoped(binds=[...]) silently produce no bean definitions with compiler plugin #34

@rduriancik

Description

@rduriancik

Environment

  • koin: 4.2.1
  • koin-annotations: 4.2.1 (via BOM)
  • io.insert-koin.compiler.plugin: 1.0.0-RC2
  • Kotlin: 2.3.21, Android

Description

When a class is annotated with @scope using the string name variant combined with @Scoped with explicit binds, the compiler plugin generates no bean definition. The app builds successfully with zero errors or warnings, but the scoped bean is completely absent from Koin's container at runtime.

Minimal Reproduction

  // Scope holder
  object SessionScope : KoinComponent {
      const val NAME = "session"                                                                                                                                                        
      const val ID   = "sessionId"
                                                                                                                                                                                        
      fun getOrCreateScope(): Scope =                                                                                                                                                   
          getKoin().getOrCreateScope(ID, named(NAME))                                                                                                                                   
  }                                                                                                                                                                                     
                                                                                  
  // Interface
  interface UserRepository { ... }                                                                                                                                                      
   
  // Implementation — annotated with string-name scope + binds                                                                                                                          
  @Scope(name = SessionScope.NAME)                                                
  @Scoped(binds = [UserRepository::class])                                                                                                                                              
  internal class UserRepositoryImpl(                                              
      private val api: ApiService,                                                                                                                                                      
  ) : UserRepository { ... }
                                                                                                                                                                                        
  // Module with ComponentScan                                                    
  @Module                                                                                                                                                                               
  @ComponentScan("com.example.data")
  class DataModule                                                                                                                                                                      
                                                                                                                                                                                        
  // Application entry point
  @KoinApplication(modules = [DataModule::class])                                                                                                                                       
  class App : Application() {                                                     
      override fun onCreate() {                                                                                                                                                         
          super.onCreate()
          startKoin<App> { androidContext(this@App) }                                                                                                                                   
      }                                                                                                                                                                                 
  }

Runtime verification:

  val koin = GlobalContext.get()
                                                                                                                                                                                        
  // Prints nothing — no scopes registered
  koin.scopeRegistry.scopeDefinitions.forEach {                                                                                                                                         
      Log.d("KOIN", "Scope: $it")                                                                                                                                                       
  }                                                                                                                                                                                     
                                                                                                                                                                                        
  // UserRepositoryImpl is absent                                                                                                                                                       
  koin.instanceRegistry.instances.values.forEach {                                
      Log.d("KOIN", "Bean: ${it.beanDefinition.primaryType.simpleName}")                                                                                                                
  }                                                                                                                                                                                     

Expected Behavior

The above should generate:

  scope(named("session")) {                                                                                                                                                             
      scoped<UserRepository> { UserRepositoryImpl(get()) }                        
  }

Actual Behavior

No bean definition for UserRepositoryImpl / UserRepository

What Does Work

The issue is specific to the combination of string-name @scope + @Scoped. Other definition types (@single, @factory, @KoinViewModel) discovered via @componentscan appear to be processed correctly.

The equivalent manual DSL module confirms the Koin scope mechanism itself is functional — only the annotation processing is broken:

  val sessionModule = module {                                                                                                                                                          
      scope(named(SessionScope.NAME)) {                                                                                                                                                 
          scoped<UserRepository> { UserRepositoryImpl(get()) }
      }                                                                                                                                                                                 
  }  

Notes

  • koin-ksp-compiler:2.3.1 handled this pattern correctly with the same annotations
  • Tested with
@Scoped
@Scope(MyScope::class)

And the class was present in bean definitions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions