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
CoolServiceis now a kotlin object, - The
DependsOnCoolServiceis a kotlin object - The
DependsOnCoolServiceTestis 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