I'm writing a JavaFX application in Kotlin with the following controller definition:
class MainController {
#Inject private lateinit var componentDescriptors: List<ComponentDescriptor>
/* More code goes here */
}
I'm using Guice for Dependency management. And I'm trying to inject the list of class instances loaded via java.util.ServiceLoader. My problem is to define a binding that will inject the list of loaded object instances into the declared field. I tried annotation based provisioning:
internal class MyModule: AbstractModule() {
override fun configure() { }
#Provides #Singleton
fun bindComponentDescriptors(): List<ComponentDescriptor> =
ServiceLoader.load(ComponentDescriptor::class.java).toList()
}
and multibinding extension (switched List to Set in field definition of corse):
internal class MyModule: AbstractModule() {
override fun configure() {
val componentDescriptorBinder = Multibinder.newSetBinder(binder(), ComponentDescriptor::class.java)
ServiceLoader.load(ComponentDescriptor::class.java).forEach {
componentDescriptorBinder.addBinding().toInstance(it)
}
}
}
but both of these approaches leads to the same error:
No implementation for java.util.List<? extends simpleApp.ComponentDescriptor> was bound.
while locating java.util.List<? extends simpleApp.ComponentDescriptor>
for field at simpleApp.MainController.componentDescryptors(MainController.kt:6)
while locating simpleApp.MainController
1 error
at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1042)
at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1001)
at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1051)
at com.gluonhq.ignite.guice.GuiceContext.getInstance(GuiceContext.java:46)
at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:929)
at javafx.fxml.FXMLLoader$InstanceDeclarationElement.processAttribute(FXMLLoader.java:971)
at javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:220)
at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:744)
at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2707)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527)
... 12 more
I'm starting to suspect that it somehow related to Kotlin gerenic variance and Guice strict type checking. But I don't know how to declare the binding so Guice will know what to inject into this field.
Yes, it happens because of variance but there's a way to make it work.
class MainController {
#JvmSuppressWildcards
#Inject
private lateinit var componentDescriptors: List<ComponentDescriptor>
}
By default Kotlin generates List<? extends ComponentDescriptor> signature for the componentDescriptors field. The #JvmSuppressWildcards makes it generate a simple parameterized signature List<ComponentDescriptor>.
#Michael gives the correct answer and explanation. Here's an example of one strategy for unit testing a Set multibinding for those that like to test their modules:
class MyModuleTest {
#JvmSuppressWildcards
#Inject
private lateinit var myTypes: Set<MyType>
#Before fun before() {
val injector = Guice.createInjector(MyModule())
injector.injectMembers(this)
}
#Test fun multibindings() {
assertNotNull(myTypes)
assertTrue(myTypes.iterator().next() is MyType)
}
}
#Michael comment is working. If you want to do the injection in constructor, you need do something like
class MainController #Inject consturctor(
private var componentDescriptors: List<#JvmSuppressWildcards ComponentDescriptor>
) {}
Related
There is an interface DCE, which is implemented by a class DCEImpl which has a dependency, say, string S, which it gets via its constructor.
The universe of S is limited, say S can only take values {'A','B','C'}.
There is an already existing Guice module that accepts the value of S in its constructor, and then binds the interface DCE to the correctly initialized version of DCEImpl.
public class DCEModule extends AbstractModule {
private final String s;
public DCEModule(String s){
this.s = s;
}
protected void configure() {
bind(DCE.class).toInstance(new DCEImpl(s));
}
}
Now I have a class C which needs a List<DCE> with all the 3 implementations (actually a lot more than 3, using 3 for example purpose).
I want to inject this list via Guice in C. To do that, I created a new module DCEPModule, which will provide a List<DCE> in this way:
#Provides
List<DCE> getDCE() {
for(String s: S){
Module m = new DCEModule(s);
install(m);
Injector injector = Guice.createInjector(m);
listDomains.add(injector.getInstance(DCE.class));
}
}
My problem is that I don't want to call a new injector in this module, because DCEPModule will be installed by a different module.
public class NewModule extends AbstractModule {
protected void configure() {
install(DCEPModule);
}
}
I want a way to get the List<DCE> without explicitly creating a new injector in DCEPModule.
You can achieve this by using a Multibinder (javadoc, wiki).
Here’s an example:
public class SnacksModule extends AbstractModule {
protected void configure(){
Multibinder<Snack> multibinder = Multibinder.newSetBinder(binder(), Snack.class);
multibinder.addBinding().toInstance(new Twix());
multibinder.addBinding().toProvider(SnickersProvider.class);
multibinder.addBinding().to(Skittles.class);
}
}
Now, the multibinder will provide a Set<Snack>. If you absolutely need a List instead of a Set, then you can add a method to your module like this:
#Provides
public List<Snack> getSnackList(Set<Snack> snackSet) {
return new ArrayList(snackSet);
}
You can add implementations to the same Multibinding in more than one module. When you call Multibinder.newSetBinder(binder, type) it doesn’t necessarily create a new Multibinding. If a Multibinding already exists for for that type, then you will get the existing Multibinding.
When I try to inject a class via constructor in a Kotlin class I am getting this exception.
I have been reading about ways to solve it in Java by adding an empty constructor to the class that gets the injection.
However, it does not seem to be possible (or I do not how) to do it in Kotlin.
The code that throws the exception has nothing out of normal...just a simple injection.
Here it is a piece of the code with the injection that throws exception:
class RemoteService #Inject constructor(
#SongListRepo private val songListRepo: SongListRepo): MainBaseService() {
...
...
...
}
And of course the SongListRepo is provided by a module in the usual Dagger 2 way.
-------- EDIT: ---------
After some help and comments from #Demigod the code looks like this
class RemoteService (): MainBaseService() {
lateinit var songLR : SongListRepo
#Inject constructor(#SongListRepo songListRepo: SongListRepo) : this(
{
this.songLR = songListRepo
}
Problem now is that secondary constructor never gets initialized
Is it possible that the error you're getting is related to this Service (I think like Fragment they must have a zero argument constructor, so that the platform would be able to re create them)? If yes than you might use an on demand injection, like:
class RemoteService: MainBaseService() {
#Inject
#SongListRepo
private lateinit var songListRepo: SongListRepo
init {
// Something like this
AndroidInjection.inject(this)
}
}
Or, anyway you can add additional constructors like:
class RemoteService constructor(): MainBaseService() {
private lateinit var songListRepo: SongListRepo? = null
#Inject constructor(#SongListRepo songListRepo: SongListRepo):this() {
this.songListRepo = songListRepo
}
}
More about constructors here
I have a class which should be created during the injection phase but this instance will not be referenced anywhere in the code. The way this class communicates with the others is via event bus
public class DefaultCounterTracker {
private final EventBus eventBus;
private final ReplicatedMap<String, String> trackerCache;
#Inject
public DefaultCounterTracker(
EventBus eventBus,
#Named("CountersTrackerCache") ReplicatedMap<String, String> trackerCache)
{
this.eventBus = eventBus;
this.trackerCache = trackerCache;
bindListeners();
}
private void bindListeners()
{
eventBus.localConsumer(CounterCreated.name(), (Handler<Message<String>>) event ->
{
handleCreation(event.body());
});
eventBus.localConsumer(CounterDestroyed.name(), (Handler<Message<String>>) event ->
{
handleDestruction(event.body());
});
}
Debugging I have seen that this class is created only when somewhere in the code there is written
#Inject
DefaultCounterTracker counterTracker
I have tried binding using a provider but nothing changes: it seems that if the class is not referenced Guice won't create it. Is there a way to tell Guice to do it? Did I miss the point?
I could also create the instance manually but if I do change in default implementation of one of the parameters (EventBus for instance) I need to remember also to change the call in the constructor.
Any suggestion appreciated
If the mentioned class is supposed to be a Singleton, then you can create an object of this class eagerly by specifying a binding in your configure() method of the AbstractModule class.
bind(DefaultCounterTracker.class).to(DefaultCounterTracker.class).asEagerSingleton();
I've got a interface with a couple of implementations:
interface PuNoManager {
fun notifyUser(userId: Int)
}
class FcmManager
#Inject
constructor(val fcmClient: FcmClient) : PuNoManager {
override fun notifyUser(userId: Int) { ... }
}
class ApnsManager
#Inject
constructor(val apnsClient: ApnsClient) : PuNoManager {
override fun notifyUser(userId: Int) { ... }
}
Which are both bound in my Module, along with a #Provides-annotated method to get a List<PuNoManager>:
class PuNoModule: AbstractModule() {
override fun configure() {
bind(ApnsManager::class.java)
bind(FcmManager::class.java)
}
#Provides
fun puNoManagers(apnsManager: ApnsManager, fcmManager: FcmManager): List<PuNoManager> {
return listOf(apnsManager, fcmManager)
}
}
The problem arises when I have a class that needs the List<PuNoManager>—Guice complains that the type hasn't been bound:
Explicit bindings are required and java.util.List<? extends ...PuNoManager> is not explicitly bound.
while locating java.util.List<? extends ...PuNoManager>
I know my Guice setup is working, as I previously had just the ApnsManager and am adding the second PuNoManager, FcmManager. The problem stems from the dependent class requesting injection of List<PuNoManager> instead of just ApnsManager.
List<X> in Kotlin is translated to java.util.List<? extends X> on the JVM. Apparently, Guice doesn't support injection of such values. To avoid the wildcard here, you can use a MutableList<X> instead, which translates to java.util.List<X>
Try adding #JvmSuppressWildcards to the module class.
I would like to have a static instance method with Guice for one of the components (non-managed bean should be able to access this class). I created something like this:
public class LookupService {
#Inject
private static Provider<Injector> injector = null;
private final ILookup<IWS> lookup;
#Inject
public LookupService(ILookup<IWS> lookup) {
this.lookup = lookup;
}
public static LookupService instance() {
return injector.get().getInstance(LookupService.class);
}
public <T extends IWS> T lookup(Class<T> localInterface) {
return lookup.lookup(localInterface);
}
}
What do you think about this design ? Any other ideas on this ? (accessing managed beans from non-managed objects)
Basically, the pattern you're looking for is called "requesting static injection" and there's a Binder method dedicated to it. Once you have that down, your code looks a lot like this example from the Guice docs.
public class MainModule extends AbstractModule {
#Override public void configure() {
requestStaticInjection(LookupService.class);
}
}
public class LookupService {
/** This will be set as soon as the injector is created. */
#Inject
static Provider<LookupService> provider = null;
private final ILookup<IWS> lookup;
#Inject
public LookupService(ILookup<IWS> lookup) {
this.lookup = lookup;
}
public static LookupService instance() {
return provider.get();
}
public <T extends IWS> T lookup(Class<T> localInterface) {
return lookup.lookup(localInterface);
}
}
A few notes:
While you can still set your field to be private, remember that this means you cannot set it in tests (or in future non-Guice usage) without Guice's private-field-access magic. When using injected fields, we often make them package-private and then put the tests in the same package.
Static injection is generally seen as something to endorse only when migrating to Guice, or when you use other code you can't change. When possible, try to avoid global state--even if this means making FooBean data-only and creating an injected FooBeanService.
Even though you can inject an Injector wherever you'd like, you might find it easier to test if you simply inject a Provider<LookupService> instead. Only inject an Injector if you don't know what type you're going to need until runtime--for example, if you implement LookupService.lookup(...) using an Injector by passing the class literal to the injector to get an instance.
In fact, it's hard to say from here, but ILookup seems to act a lot like the Service Locator pattern, which solves the exact type of problem that Guice solves with dependency injection! If that's the case, you might as well rewrite ILookup to use Guice: Just remove calls to LookupService.instance().lookup(Foo.class) and instead create a matching pair of #Inject static Provider<Foo> fooProvider and requestStaticInjection(FooUser.class).
Hope that helps!