kotlin-inject icon indicating copy to clipboard operation
kotlin-inject copied to clipboard

Add default values for component constructor arguments which are components with no constructor arguments

Open japplin opened this issue 4 years ago • 6 comments

Example Code:

@Component abstract class NoArgComponent {
@get:Provides val string: String = "Hi"
}

@Component abstract class MyComponent(@Component component: NoArgComponent)

Generates:

InjectMyComponent(component: NoArgComponent = InjectNoArgComponent()) : MyComponent(component)

This feature provides functionality similar to how dagger can build modules which don't have any constructor params.

japplin avatar Aug 27 '20 18:08 japplin

Interesting idea! One thing to note is that I'm leaning towards suggesting to use inheritance for combining things like you would with modules whereas component dependencies would be used when you have multiple different component lifetimes. So in your case you'd do:

interface NoArgComponent {
   @get:Provides val string: String get() = "Hi"
}

@Component abstract class MyComponent : NoArgComponent

If you use separate components for different lifetimes then it doesn't make sense to have it as a default parameter since you'd want to hold a reference to it separately. However, there may be some limitations to this approach so there may be cases where your suggestion is useful, haven't figured that out yet.

evant avatar Aug 27 '20 18:08 evant

One thing is default values you declare should probably be propagated, so you could do:

@Component abstract class MyComponent(@Component component: NoArgComponent = NoArgComponent())

MyComponent::class.create()

at least

evant avatar Aug 27 '20 18:08 evant

One thing is default values you declare should probably be propagated, so you could do:

@Component abstract class MyComponent(@Component component: NoArgComponent = NoArgComponent())

MyComponent::class.create()

at least

Yea this feature is also a good addition, I tried doing this once already and realized it didn't support that yet.

japplin avatar Aug 27 '20 18:08 japplin

I think the main limitation of component extension is it leaks the types of the "module" into the components public definition:

class OkHttpClient
@Inject class MyApi(private val myApiClient: OkHttpClient)

@Component interface HttpClient {
    @get:Provides val client: OkHttpClient get() = OkHttpClient()
}
@Component abstract class ApiComponent : HttpClient {
    @get:Provides abstract val api: MyApi
}

fun test() {
    ApiComponent::api
    ApiComponent::client
}

In this example I don't really want the client to be accessible from the api component just the final api type.

japplin avatar Aug 27 '20 19:08 japplin

Yep that's certainly a limitation. If you could make methods on interfaces protected that would fix this, but alas you can't. One option would be to have a separate 'public api' interface, but I'm not sure I like that.

interface ApiComponent {
   val api: Api
}

@Component abstract class ApiComponentImpl : ApiComponent, HttpClient

val component: ApiComponent = ApiComonentImpl::class.create()

evant avatar Aug 27 '20 19:08 evant

Just merged in support for omitting default args, I'm hopping this will be enough, but we can revisit if there is a good reason to.

evant avatar Oct 17 '20 19:10 evant

Closing with the assumption the current implementation is good enough

evant avatar Feb 11 '23 22:02 evant