Coder Social home page Coder Social logo

Comments (3)

JakeWharton avatar JakeWharton commented on June 3, 2024

This has been discussed previously:

from dagger.

laneb avatar laneb commented on June 3, 2024

@JakeWharton Thanks for the links! Good to know I'm not alone in wanting such a feature :^)

It seems like #2026 is the most thorough discussion of the limitations and implications that would make such a feature unfeasible; however, I think the approach I suggest mitigates those concerns. I would like to press this a bit, because a feature like I describe would make Dagger significantly less annoying for my use case. If this discussion would be more welcome in one of the linked tickets, please let me know.

Particularly

There's no way for Dagger to know that your type should be added to the set in its normal traversal.

I did anticipate this - I assume it's complicated and prohibitively slow to search the class path for all subtypes of the injected type. I'm sure that injecting Set<Any> without any constraint on which Any you're interested in would be a fun corner case. This is why I suggest constraining the @IntoSet or equivalent annotation with the specific type of the collection to be injected (IntoSet(::ServiceInterface) in my example). In fact, I see this idea mentioned briefly in #2026

Dagger would either need to ... allow you to pass that information in via the annotation (which doesn't work well with parameterized types)

For my use case, at least, this is a non-issue. And if I did really need to inject a parameterized type, there are workarounds as long as I'm willing to shape my code for Dagger's better understanding, which I am. For example, if we rework the example like

interface ServiceInterface<Result>

interface A
interface A1: A
interface A2: A
interface B

class ServiceA1 @Inject internal constructor(...): ServiceInterface<A1>
class ServiceA2 @Inject internal constructor(...): ServiceInterface<A2>
class ServiceB @Inject internal constructor(...): ServiceInterface<B>

and I want to inject a Set<ServiceInterface<A>> into my application, I could add an intermediate interface, along the lines of

interface AServiceInterface: ServiceInterface<A>

class ServiceA1 @Inject @IntoSet<AServiceInterface::class> internal constructor(...): AServiceInterface
class ServiceA2 @Inject @IntoSet<AServiceInterface::class> internal constructor(...): AServiceInterface

class App @Inject internal constructor(
  private val services: Set<@JvmSuppressWildcards AServiceInterface<*>>,
)

Not ideal, but certainly workable and significantly preferable to enumerating the service implementations in a module.

As for the other concerns raised

There's no way to remove the type from the set. For example, you still want to inject Set but you don't want that particular class in the set. With modules you just remove the module, but with this solution you would have to remove the class from your class path, which can either be difficult or impossible if it's used elsewhere.

One of the useful features of multibindings is that the set can change depending on which component you install the module int. With this solution, you don't choose a component so it's ambiguous where Dagger should install it.

Acknowledged that the modules provide more fine-grained control over what instances are included in the Set, but as I have observed the tradeoff is more boilerplate. It seems to me there is room for both approaches.

from dagger.

Chang-Eric avatar Chang-Eric commented on June 3, 2024

I did anticipate this - I assume it's complicated and prohibitively slow to search the class path for all subtypes of the injected type. I'm sure that injecting Set without any constraint on which Any you're interested in would be a fun corner case. This is why I suggest constraining the @IntoSet or equivalent annotation with the specific type of the collection to be injected (IntoSet(::ServiceInterface) in my example).

It isn't just a matter of constraining. Searching the classpath is just something Dagger doesn't do and probably isn't something we want it to do in the future. This basically limits the solution space to Hilt where we could generate a Dagger module.

Not ideal, but certainly workable and significantly preferable to enumerating the service implementations in a module.

The issues with things like qualifiers and parameterized types are things you can workaround, if you know about them. But that is actually the main issue. I don't want to create a new API for doing something just to save on boilerplate, but then in addition to the cognitive overhead of having two ways to do something, you also have the overhead of learning certain features don't work with other features. IMO, it isn't worth the tradeoff as we already know it is difficult for people to get up to speed on Dagger features and there is already a fair amount of multiple ways to do the same thing that adds confusion.

I feel like if we're going to add the cognitive overhead cost of doing something multiple ways, the other way has to be pretty clean and intuitive and cover a lot of cases. So far, I just haven't seen anything or come up with anything that meets that. Closing this since #2026 (comment) I think also covers a lot of the current thoughts on this.

Separate aside:

Separately, I'm going to assume your example falls into this other issue because it is just an example and that this probably doesn't apply to you, but just for others who may be reading this since I have seen this in code before, if you have a single module that defines ALL of your set contributions, you don't need multibindings.

@Module
interface ServiceModule {
  @Bind
  @IntoSet
  fun service1(service: Service1): ServiceInterface

  @Bind
  @IntoSet
  fun service2(service: Service2): ServiceInterface
  
  ...

  @Bind
  @IntoSet
  fun serviceN(service: ServiceN): ServiceInterface
}

Should just be rewritten as

@Module
interface ServiceModule {
  @Provides
  fun services(service1: Service1, service2: Service2, ..., serviceN: ServiceN): Set<ServiceInterface> {
    return setOf(service1, service2, ..., serviceN)
  }
}

You only need multibindings if you need to make contributions from multiple sites where no single piece of code knows what all of the contributions should be.

from dagger.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.