How do I pass AppCompatActivity to module? - dagger-2

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
}

Related

Play Framework without dependency injection?

Without going into why, say someone wanted an old-fashioned Play Framework web service and did not want to use dependency injection nor rely on Google's Guice. Is it still possible in Play 2.8.x?
The api documentation along with the current Play examples recommend this as a "typical" HomeController.scala:
package controllers
import javax.inject._
import play.api.mvc._
class HomeController #Inject() (val controllerComponents: ControllerComponents) extends BaseController {
def index = Action {
Ok("It works!")
}
}
My desired code is the same, but without #Inject() (similar to when I last used Play 2.4.0 in 2016)? Back in the day my code looked like this:
package controllers
import play.api.mvc.{Action, AnyContent, Controller}
object TestController {
def index:Action[AnyContent] = Action {
Ok("It used to work.")
}
}
Console:
[info] Compiling 1 Scala source to /Volumes/.../play-scala-seed/target/scala-2.13/classes ...
[error] p.a.h.DefaultHttpErrorHandler -
! #7ef69nl6l - Internal server error, for (GET) [/test/] ->
play.api.UnexpectedException: Unexpected exception[CreationException: Unable to create injector, see the following errors:
1) Could not find a suitable constructor in controllers.TestController. Classes must have either one (and only one) constructor annotated with #Inject or a zero-argument constructor that is not private.
at controllers.TestController.class(TestController.scala:3)
while locating controllers.TestController
for the 4th parameter of router.Routes.<init>(Routes.scala:33)
at play.api.inject.RoutesProvider$.bindingsFromConfiguration(BuiltinModule.scala:137):
Binding(class router.Routes to self) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$4)
1 error]
at play.core.server.DevServerStart$$anon$1.reload(DevServerStart.scala:210)
at play.core.server.DevServerStart$$anon$1.get(DevServerStart.scala:141)
at play.core.server.AkkaHttpServer.handleRequest(AkkaHttpServer.scala:296)
at play.core.server.AkkaHttpServer.$anonfun$createServerBinding$1(AkkaHttpServer.scala:186)
at akka.stream.impl.fusing.MapAsync$$anon$30.onPush(Ops.scala:1261)
at akka.stream.impl.fusing.GraphInterpreter.processPush(GraphInterpreter.scala:541)
at akka.stream.impl.fusing.GraphInterpreter.execute(GraphInterpreter.scala:423)
at akka.stream.impl.fusing.GraphInterpreterShell.runBatch(ActorGraphInterpreter.scala:624)
at akka.stream.impl.fusing.GraphInterpreterShell$AsyncInput.execute(ActorGraphInterpreter.scala:501)
at akka.stream.impl.fusing.GraphInterpreterShell.processEvent(ActorGraphInterpreter.scala:599)
Caused by: com.google.inject.CreationException: Unable to create injector, see the following errors:
1) Could not find a suitable constructor in controllers.TestController. Classes must have either one (and only one) constructor annotated with #Inject or a zero-argument constructor that is not private.
at controllers.TestController.class(TestController.scala:3)
while locating controllers.TestController
for the 4th parameter of router.Routes.<init>(Routes.scala:33)
at play.api.inject.RoutesProvider$.bindingsFromConfiguration(BuiltinModule.scala:137):
Binding(class router.Routes to self) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$4)
1 error
at com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:543)
at com.google.inject.internal.InternalInjectorCreator.initializeStatically(InternalInjectorCreator.java:159)
at com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:106)
at com.google.inject.Guice.createInjector(Guice.java:87)
at com.google.inject.Guice.createInjector(Guice.java:78)
at play.api.inject.guice.GuiceBuilder.injector(GuiceInjectorBuilder.scala:200)
at play.api.inject.guice.GuiceApplicationBuilder.build(GuiceApplicationBuilder.scala:155)
at play.api.inject.guice.GuiceApplicationLoader.load(GuiceApplicationLoader.scala:21)
at play.core.server.DevServerStart$$anon$1.$anonfun$reload$3(DevServerStart.scala:189)
at play.utils.Threads$.withContextClassLoader(Threads.scala:21)
Is there a simple fix to stay old school--without going here?
I acknowledge but don't totally understand https://www.playframework.com/documentation/2.4.x/Migration24. I assume my problem has to do with static routing having been removed in 2.7.
My reputation doesn't allow me to comment to the answer by Mario Galic, but you can easily modify his example by using the "right" (non-test) controllerComponents that are provided by BuiltInComponentsFromContext.
The whole example would look like
class HomeController(override protected val controllerComponents: ControllerComponents)
extends BaseController {
def index = Action { Ok("It works!") }
}
class MyApplicationLoader extends ApplicationLoader {
def load(context: ApplicationLoader.Context): Application = {
new BuiltInComponentsFromContext(context) {
lazy val homeController = HomeController(controllerComponents)
override lazy val router: Router = Routes(httpErrorHandler, homeController)
}.application
}
}
To answer comment by #kujosHeist about an equivalent example in Java. This seems to work for me (Following the Play docs):
package controllers;
import play.mvc.Controller;
import play.mvc.Result;
public class HomeController extends Controller {
public Result index() {
return ok("It works!");
}
}
public class MyApplicationLoader implements ApplicationLoader {
#Override
public Application load(Context context) {
return new MyComponents(context).application();
}
}
class MyComponents extends BuiltInComponentsFromContext implements HttpFiltersComponents, AssetsComponents {
public MyComponents(ApplicationLoader.Context context) {
super(context);
}
#Override
public Router router() {
HomeController homeController = new HomeController();
Assets assets = new Assets(scalaHttpErrorHandler(), assetsMetadata());
return new router.Routes(scalaHttpErrorHandler(), homeController, assets).asJava();
}
}
You might want to define things like e.g. error handlers differently, but this could be roughly the structure.
Indeed StaticRoutesGenerator has been removed which is need to have controllers as singleton objects. Perhaps using compile time dependency injection, with an example here, might bring you closer to what you were used to, however ControllerComponents will still need to be injected. Technically, it might be possible to do something ill-advised by putting play-test on the Compile classpath and make use stubControllerComponents like so
class HomeController extends BaseController {
def index = Action { Ok("It works!") }
override protected def controllerComponents: ControllerComponents =
play.api.test.Helpers.stubControllerComponents()
}
and corresponding minimal ApplicationLoader
class MyApplicationLoader extends ApplicationLoader {
def load(context: ApplicationLoader.Context): Application = {
new BuiltInComponentsFromContext(context) {
override def httpFilters: Seq[EssentialFilter] = Nil
lazy val homeController = new _root_.controllers.HomeController
lazy val router: Router = new _root_.router.Routes(httpErrorHandler, homeController)
}.application
}
}
This way HomeController, although still a class, is now completely hardwired, and there is only a single instance of it created in ApplicationLoader.
Personally, I would advise against such shenanigans, and believe there are good arguments why Play moved away from singletons, for example, testability, thread-safety, etc.

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

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?

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"
}

Can't inject actorSystem in service

I am trying to create a service that runs in the background of my app (reads and writes to a queue) that I want to build with the actor system. However I am getting an error when I try to inject an ActorSystem into my class:
play.api.UnexpectedException: Unexpected exception[CreationException: Unable to create injector, see the following errors:
1) Error injecting constructor, java.lang.IllegalArgumentException: no matching constructor found on class services.Indexer$IndexActor for arguments []
at services.Indexer.<init>(Indexer.scala:21)
at Module.configure(Module.scala:6) (via modules: com.google.inject.util.Modules$OverrideModule -> Module)
while locating services.Indexer
Here is my setup:
// Module.scala
class Module extends AbstractModule {
override def configure() = {
bind(classOf[Indexer]).asEagerSingleton() // I suspect this needs to change
}
}
// Indexer.scala
#Singleton
class Indexer #Inject() (appLifecycle: ApplicationLifecycle, system: ActorSystem) (implicit ec: ExecutionContext) { ... }
In the play documentation there is an example of injecting an actor system, but that only seems to work when you inject into a class which extends Controller:
#Singleton
class MyController #Inject()(system: ActorSystem)(implicit exec: ExecutionContext) extends Controller {
// This works
}
Found the problem:
In Module.scala I am using the com.google.inject package
import com.google.inject.AbstractModule // <-------
import services.Indexer
class Module extends AbstractModule {
override def configure() = {
bind(classOf[Indexer]).asEagerSingleton()
}
}
But in my Indexer.scala service I was using the javax.inject package.
I have switched all of my files to now:
import com.google.inject._

Play framework inherit dependency injected trait?

Is it possible to create an overloaded play.api.mvc.Controller trait that has dependency injected arguments?
For example, say I have a couple of customized Actions that require a dependency injected AuthorizationService. I would like to write my controllers like this:
class UserController extends CustomController {
def getUser(userID: String) = CustomAction {
...
}
}
However, I can't figure out how to create the CustomController trait such that it doesn't require me to inject my AuthorizationService in my UserController. Is there a way to do this with Guice?
You can inject a field into your CustomController trait. The field should'n be final so it has to be declared as var in Scala.
#Inject() var authService: AuthorizationService
You can also make the injected var private and declare a public val which references the injected field. In this case val has to be lazy since injection occurs after class was instantiated. See Guice docs for more details.
#Inject() private var as: AuthorizationService = _
lazy val authService: AuthorizationService = as
It isn't possible to inject a dependency into a trait because a trait isn't instantiable. A trait doesn't have a constructor to define the dependencies, you must inject your AuthService via UserController
Example.
trait CustomController extends Controller {
val authService: AuthService
...
}
class UserController #Inject()(override val authService: AuthService) extends CustomController {
...
}