Hi, could someone help me figure out how to have t...
# kotlin-inject
d
Hi, could someone help me figure out how to have the correct visibility modifiers when using kotlin-inject? Basically I have a KMP project with an Android and an iOS app consuming + shared module. I want my "Repositories" to have
internal
visibility modifier but I'm running into some issues. Details in thread
Assuming I have something like this
Copy code
internal interface FooRepository { }

internal class FooRepositoryImpl() : FooRepository
I then need to define my the DI as
Copy code
internal interface SharedRepositoryComponent {
    @Provides
    @Singleton
    fun FooRepositoryImpl.bind(): FooRepository = this
}
this all seems fine but then in the Android side, for my view model factories I get the following code generated
Copy code
public class BarViewModelFactory(
  parent: AndroidApplicationComponent,
) : BarViewModelFactory(parent) {
  override val factory: () -> BarViewModelFactoryImpl
    get() {
      require(parent is ScopedComponent)
      return {
        BarViewModelFactoryImpl(
          viewModel = {
            BarViewModel(
              fooUseCase = (parent.sharedComponent as
                  ScopedComponent)._scoped.get("package.FooUseCase") {
                with(parent.sharedComponent) {
                  (this as
                      ScopedComponent)._scoped.get("package.FooUseCaseImpl") {
                    FooUseCaseImpl(
                      accountsRepository = (this as
                          ScopedComponent)._scoped.get("package.FooRepository") {
                        (this as
                            ScopedComponent)._scoped.get("package.fooRepositoryImpl") {
                          FooRepositoryImpl()
                        }.bind()
                      }
                    )
                  }.bind()
                }
              }
            )
          }
        )
      }
    }
}
Which of course produces an error of
Copy code
Cannot access 'FooRepositoryImpl': it is internal in 'package'
Could someone point me in the right direction? I feel like this should be a fairly common case that people have -
internal
modifier helps reduce the size of the objc header so I assume everyone will try to have as little
public
classes in their shared module as possible
Copy code
BarViewModel(
  fooUseCase = (parent.sharedComponent as ScopedComponent)._scoped.get("package.FooUseCase") {
    with(parent.sharedComponent) {
      (this as ScopedComponent)._scoped.get("package.FooUseCaseImpl") {
        FooUseCaseImpl( // <--- Here
          accountsRepository = (this as ScopedComponent)._scoped.get("package.FooRepository") {
            (this as ScopedComponent)._scoped.get("package.fooRepositoryImpl") {
              FooRepositoryImpl() // <--- Here
            }.bind()
          }
        )
      }.bind()
    }
  }
)
Tldr - in snippet, the fact that
_scoped.get()
gets passed an initialiser means that it needs to be able to access the classes which are now internal and defined in a different module There's every chance that my view model generation factory code is wrong and what's causing the error but it didn't seems so to me and I'd like a 2nd opinion (or at least someone to tell me what I'm after is possible) before I sink tons of time into changing that...
m
have you ever found an answer for this @dorche? I have the same question, except that my interfaces are usually public, just the implementation is internal, so more like this:
Copy code
interface FooRepository { }

internal class FooRepositoryImpl() : FooRepository
these are the "workarounds" I've found from other modularized projects: 1. not use
internal
, but that's not what you and I want, however personally I'd be open to being convinced that I don't need an internal class because kotlin-inject folks can solve the same problems in a different way 2. use `@Component abstract class`es instead of interfaces for the
@Provides
methods. In an interface you can't make your methods internal, but in an abstract class you can, so this works
internal fun FooRepositoryImpl.bind(): FooRepository = this
. The downside here is that your AppComponent can't inherit from multiple abstract classes, you'll have to actually inject (and build) those components 3. I suppose you have
FooRepositoryImpl
annotated with
@Inject
and this last workaround is to drop that annotation and build it yourself, but I find this kinda kills the purpose of a DI framework, although it might exceptionally be an acceptable tradeoff:
Copy code
interface SharedRepositoryComponent {
    @Provides
    @Singleton
    fun providesFooRepository(
        foo: Foo,
        bar: Bar
    ): FooRepository = FooRepositoryImpl(
        foo = foo,
        bar = bar
    )
}
d
No I haven't really found an answer. Thanks for sharing your findings. I'd say option 1 is a big no-go for me, because the whole reason I'm asking this is because I want more
internal
modifiers, not less. I have a "shared" KMP module that gets consumed by my iOS project (native UI but KMP for everything else) so with all this I'm trying to reduce what gets exposed in the Obj-C header file. Option 2 sounds okay tbh, as long as it doesn't turn out to be ton of boilerplate when try it out. Option 3 is also just about fine, I'll have a little play with both 2 and 3 to make sure I'm not missing some big downside for either. Thank you again for sharing!
🙌 1