Kotlin and SAM interface with two parameters

another +1 to this, because without it I’m going to have to reinvent some idioms I have around guice.

In java, if a class has non-guice-time dependency, we use AssistedInject, so one example of such a class might be

class CoolService{
  public @FunctionalInterface Factory{
    CoolService create(int importantValue)
  }

  @Inject
  CoolService(Component1 comp, @Assisted int importantValue){ 
    //...
  }
}

for consumers of CoolService (that is to say components that depend on it, typically controllers of one sort or another), they would inject this factory:

class DependsOnCoolService{
  DependsOnCoolService(CoolServiceFactory svcFactory){
    //...
  }
}

this is nice from a testing environment, since we can stub that factory out nicely with a mock or stub or something.

class DependsOnCoolServiceTest{
  @Test doSomething(){
    //setup
    DependsOnCoolService componentUnderTest = new DependsOnCoolService(
        x -> mock(CoolService.class)
    );

    //... 
  }
}

The actual plumbing at runtime code is to configure guice to use the factory to create a DependsOnCoolService:

Guice.createInjector(binder -> {
  binder.install(new FactoryModuleBinding().build(CoolService.Factory.class));
  //...

notice we explicitly leverage javas SAM to convert the lambda x -> mock(... to a CoolService.Factory.

The same idiom now doesn’t work in kotlin. So what can we do instead?

  • The equivalent CoolService is now a kotlin object,
  • The DependsOnCoolService is a kotlin object
  • The DependsOnCoolServiceTest is a kotlin object
  • The guice plumbing is sill done in java, though thats easy enough to port.

so, whats our new idiom? I can go back to anonymous nested class, but that’s no fun at all, and I’m drawing a blank on an alternative.

Instead of taking a CoolServiceFactory we could ‘lift’ that to a svcFactory : () -> CoolService, which places the burden on the guice configuration… which I just attempted, and unfortunately theres something between kotlins implied contravariance and guice that dont agree with each other.

Guice.createInjector(object : Module{
    override fun configure(binder: Binder) {
        binder.install(FactoryModuleBuilder()
                .implement(OPYLConfigurationApplier::class.java, OPYLConfigurationApplier::class.java)
                .build(object : TypeLiteral<kotlin.jvm.functions.Function0<CoolService>>(){})
        )
    }
})

//throws 
com.google.inject.CreationException: Unable to create injector, see the following errors:

1) An exception was caught and reported. Message: Expected a Class, ParameterizedType, or GenericArrayType, but <? extends com.my_company.CoolService> is of type com.google.inject.internal.MoreTypes$WildcardTypeImpl
  at com.empowerops.api.OPYLImporterFixture$canGuice$1.configure(OPYLImporterFixture.kt:25) (via modules: com.empowerops.api.OPYLImporterFixture$canGuice$1 -> com.google.inject.assistedinject.FactoryModuleBuilder$1)

1 error

would really like SAM conversion in kotlin