Dagger 2 BindsInstance is not supported for ChildComponent that is bound through ParentComponent? - dagger-2

We could either have
#Subcomponent(modules = [XModule::class, YModule::class])
interface ChildComponent
#Singleton
#Component
interface ParentComponent {
fun childComponent(x: XModule, y: YModule): ChildComponent
}
or
#Subcomponent(modules = [XModule::class, YModule::class])
interface ChildComponent {
#Subcomponent.Builder
interface Builder {
fun xModule(x: XModule): Builder
fun yModule(y: YModule): Builder
fun build(): RequestComponent
}
}
#Singleton
#Component
interface ParentComponent {
fun childComponentBuilder: ChildComponentBuilder
}
However, we can have
#Subcomponent
interface ChildComponent {
#Subcomponent.Builder
interface Builder {
fun data(#BindsInstance data: Data): Builder
fun build(): RequestComponent
}
}
#Singleton
#Component
interface ParentComponent {
fun childComponentBuilder: ChildComponentBuilder
}
But not
#Subcomponent
interface ChildComponent
#Singleton
#Component
interface ParentComponent {
fun childComponent(#BindsInstance data: Data): ChildComponent
}
It complaints
error: Subcomponent factory methods may only accept modules, but com.elyeproj.daggertwomultibindings.subcomponentthroughcomponent.Data is not.
com.elyeproj.daggertwomultibindings.subcomponentthroughcomponent.Data data, #org.jetbrains.annotations.NotNull()
^
I thought this support is already done through https://github.com/google/dagger/issues/616?

Related

Creating a AbstractModule to inject a dependency for a 3rd party library

I have a 3rd party library that I am trying to inject the configuration into the constructor.
This is what I need to do:
class MyModule(configuration: Configuration) extends AbstractModule {
override def configure(): Unit = {
bind(classOf[TwitterApi])
.to(classOf[MyTwitterApi])
.asEagerSingleton
}
}
The constructor of MyTwitterApi doesn't take a Play.api.Configuration but a typesafe.config.Config
class MyTwitterApi(config: Config) ...
So I need to do pass configuration.underlying to my constructor, how is this possible using DI in this AbstractModule?
I need this instance to be a singleton also.
You can use provider to setup your module with eagerSingleton
import com.google.inject.{AbstractModule, Provider}
class MyModule(configuration: Configuration) extends AbstractModule {
override def configure(): Unit = {
val twitterApiProvider: Provider[TwitterApi] =
() => new MyTwitterApi(configuration.underlying)
bind(classOf[TwitterApi])
.toProvider(twitterApiProvider)
.asEagerSingleton
}
}
You can find a working example with sample classes at - https://scastie.scala-lang.org/sarveshseri/ujwvJJNnTpiWDqdkBJQoFw/2
I think you want something like this:
class MyModule(configuration: Configuration) extends AbstractModule {
override def configure(): Unit = {
val myTwitterApiInstance = new MyTwitterApi(configuration.underlying)
bind(classOf[TwitterApi])
.toInstance(myTwitterApiInstance)
}
}
Or another approach would be to provide a binding for Config but if your MyTwitterApi doesn't have #Inject annotation this won't help.

Why #ContributesAndroidInjector doesn't provide Android Framework type

I have simplified my application to get the root of the problem and here is the simplified version. I'm implementing Dagger 2 using following configuration:
AppComponent
#Component(modules = [
AndroidSupportInjectionModule::class,
ActivityBindingModule::class
])
interface AppComponent: AndroidInjector<MyApp> {
#Component.Builder
interface Builder{
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
}
ActivityBindingModule
#Module
abstract class ActivityBindingModule {
#ContributesAndroidInjector
abstract fun mainActivity(): MainActivity
#Module
companion object{
#JvmStatic
#Provides
fun provideString(mainActivity: MainActivity): String{
return "Tent"
}
}
}
MainActivity
class MainActivity : DaggerAppCompatActivity() {
#Inject
lateinit var string: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
println("meco simplest ${string}")
}
}
When I run the application I get the following error. What I don't understand is ContributesAndroidInjector is already providing an instace of MainActivity why Dagger still complains about it.
MainActivity cannot be provided without an #Inject constructor or an
#Provides-annotated method
EDIT for #yavor
Keep all classes as is and separate ActivityBindingModule implementation into two classes. Now you can see that instance of the MainActivity is provided and Dagger is not complaining about it.
ActivityBindingModule
#Module
abstract class ActivityBindingModule {
#ContributesAndroidInjector(modulese [StringProviderModule::class])
abstract fun mainActivity(): MainActivity
}
StringProviderModule
#Module
class StringProviderModule {
#Module
companion object{
#JvmStatic
#Provides
fun provideString(mainActivity: MainActivity): String{
return "Tent"
}
}
}
What I don't understand is ContributesAndroidInjector is already providing an instace of MainActivity why Dagger still complains about it.
ContributesAndroidInjector in docs says:
Generates an {#link AndroidInjector} for the return type of this method. The injector is implemented with a {#link dagger.Subcomponent} and will be a child of the {#link dagger.Module}'s component.
So it does not provide MainActivity.
Why do you need it actually at all? I see that you are passing it as parameter to the function:
fun provideString(mainActivity: MainActivity)
but do you really need it there? In general you should inject dependencies in MainActivity. MainActivity should use them. If both(MainActivity and the string) they know about each other it is first - not a good design, and second: you are close to create cyclic dependencies which Dagger 2 does not support and throws exception.
You probably forgot to inject your application in MyApp. You should have something like this (you might need to modify it a bit to fit your AppComponent:
DaggerAppComponent.builder()
.application(this)
.build()
.inject(this)
Also, Dagger is actually providing your MainActivity through your #ContributesAndroidInjector annotated method but that's not what you're injecting.
You're injecting a string so Dagger is using your provideString method. Since this method requires a MainActivity instance to work, Dagger is looking for such a method annotated with #Provides. You don't have any and Dagger won't look at your #ContributesAndroidInjector method since it does not have any reasons to do so.
If you want it to work, you actually have to define your provideString method in a separate module and install it inside your #ContributesAndroidInjector:
#Module
abstract class ActivityBindingModule {
#ContributesAndroidInjector(modules = [StringModule::class])
abstract fun mainActivity(): MainActivity
}
#Module
class StringModule {
#JvmStatic
#Provides
fun provideString(mainActivity: MainActivity): String = "Hehe"
}

How do I pass AppCompatActivity to module?

I use Dagger 2 in my project. I have several modules in the project.
And I have a BaseActivityModule that connects to each Activity module.
How to get AppCompatActivity in BaseActivityModule.
When compiling a project, I get this error
error: [Dagger/MissingBinding] [dagger.android.AndroidInjector.inject(T)] android.app.AlertDialog cannot be provided without an #Inject constructor or an #Provides-annotated method.
public abstract interface AppComponent {
^
android.app.AlertDialog is injected at
com.example.laptop.daggerexample.ui.main.view.MainActivity.alertDialog
com.example.laptop.daggerexample.ui.main.view.MainActivity is injected at
dagger.android.AndroidInjector.inject(T)
component path: com.example.laptop.daggerexample.di.component.AppComponent ? com.example.laptop.daggerexample.di.builder.ActivityBuilder_BindMainActivity.MainActivitySubcomponent
Example below modules in the project
ActivityBuilder
#Module
abstract class ActivityBuilder {
#ContributesAndroidInjector(modules = [(MainActivityModule::class)])
abstract fun bindMainActivity(): MainActivity
}
AppModule
#Module
abstract class AppModule{
#Provides
#Singleton
fun provideContext(application: Application): Context = application
}
BaseActivityModule
#Module
class BaseActivityModule {
#Provides
fun provideAlertDialog(activity: AppCompatActivity)=
AlertDialog.Builder(activity).create()
}
MainActivityModule
#Module(includes = [(BaseActivityModule::class)])
class MainActivityModule {
#Provides
fun provideMainMVPView(activity: MainActivity): MainMVPView = activity
#Provides
fun provideMainMVPPresenter(presenter: MainPresenter<MainMVPView>):
MainMVPPresenter<MainMVPView> = presenter
}

MacWire: Is it possible to get automatic wiring of recursively dependent case classes, as in #Inject with Guice?

The following code fails at compile time:
object Foo {
case class A()
case class B(a: A)
case class C(b: B)
lazy val a = wire[A]
// Error:(14, 22) Cannot find a value of type: [QuickMain.B]
lazy val c = wire[C]
}
Is it possible to get macwire to automatically infer that it can create a B by creating an A (Honestly, lazy val a = wire[A] shouldn't even be necessary)? If macwire can't do it, is there another Scala framework that can do it in a typesafe manner (I know of Dagger, but I'm looking for a scala-based solution).
To illustrate, in Guice I could do the following:
public class JavaExample {
public static class A {}
public static class B {
#Inject
public B(A a) {}
}
public static class C {
#Inject
public C(B b) {}
}
public static void main(String[] args) {
// No bindings necessary!
var injector = Guice.createInjector();
System.out.println(injector.getInstance(C.class));
}
}

Is there a nice Play2 way of injecting instances in Play Plugins with Guice

I'm trying to figure out how to inject my classes with Google Guice into a play.api.Plugin.
I have implemented Guice to work with my controllers and it works great.
I use:
"com.google.inject" % "guice" % "4.0-beta",
"com.tzavellas" % "sse-guice" % "0.7.1"
When a Controller instance is needed the getControllerInstance method in Global will load the appropriate implementation thanks to the injector.
Global:
object Global extends GlobalSettings {
/**
* Currently we only want to load a different module when test.
*/
private lazy val injector = {
Logger.info("Is Test: "+Play.isTest)
Play.isTest match {
case true => Guice.createInjector(new TestModule)
case false => Guice.createInjector(new CommonModule)
}
}
override def onStart(app: Application) {
Logger.info("Application has started")
}
override def onStop(app: Application) {
Logger.info("Application shutdown...")
}
override def getControllerInstance[A](clazz: Class[A]) = {
Logger.info("getControllerInstance")
injector.getInstance(clazz)
}
}
Common:
package modules
import com.tzavellas.sse.guice.ScalaModule
import services.{CallServiceImpl, CallService}
/**
* User: jakob
* Date: 11/5/13
* Time: 10:04 AM
*/
class CommonModule extends ScalaModule {
def configure() {
bind[CallService].to[CallServiceImpl]
}
}
class TestModule extends ScalaModule {
def configure() {
// Test modules!
}
}
Controller:
#Singleton
class StatsController #Inject()(callService: CallService) extends Controller with securesocial.core.SecureSocial with ProvidesHeader {
def doSomething = {
callService.call()
}
}
Now I would like to inject the same service into my Plugin, but I can't make use of the Global implementation since the plugins do not load with the getControllerInstance
class CallerPlugin (application: Application) extends Plugin {
val secondsToWait = {
import scala.concurrent.duration._
10 seconds
}
val defaultInterval = 60
val intervalKey = "csv.job.interval"
val csvParserEnabled = "csv.job.enabled"
val newDir = "csv.job.new.file.path"
val doneDir = "csv.job.done.file.path"
var cancellable: Option[Cancellable] = None
override def onStop() {
cancellable.map(_.cancel())
}
override def onStart() {
// do some cool injection of callService here!!!
import scala.concurrent.duration._
import play.api.libs.concurrent.Execution.Implicits._
val i = current.configuration.getInt(intervalKey).getOrElse(defaultInterval)
cancellable = if (current.configuration.getBoolean(csvParserEnabled).getOrElse(false)) {
Some(
Akka.system.scheduler.schedule(0 seconds, i minutes) {
callService.call()
})
} else None
}
}
I guess there should be a way of implementing the injection in the onStart method somehow and there is probably some nice easy way of doing this but I can't figure it out.
Thank you!
If I understood your question correctly, you're wondering how to instantiate and use a Guice injector. Well it's really simple:
val injector = Guice.createInjector(new CommonModule)
val callService = injector.getInstance(classOf[CallService])
And like that you have an instance of CallServiceImpl. If you look at your Global.scala, this is exactly what you do there. I haven't used Play plugins, so I'm not sure how you instantiate them, but I think a more idiomatic way would be, instead of putting it in plugin's onStart, to inject this CallService as a parameter to CallerPlugin (like you do for the controller). That way you could pass the responsibility of dependency injection down the tree, so that ideally you would end up with only one injector (probably in Global).
From what I found so far the best way to achieve this is to set plugin's dependency in Global.onStart().
public class Global extends GlobalSettings{
public Injector injector = createInjector();
private Injector createInjector(){
return Guice.createInjector(new SomeGuiceModule());
}
#Override
public void onStart(Application application) {
CallerPlugin plugin = application.plugin(CallerPlugin.class);
plugin.setCallService(injector.getInstance(CallService.class));
}
}
Make sure that plugin number is lower that 10000. Global has 10000, so, your plugin will be started before Global.onStart() gets called.