43

Koin is a new, lightweight library for DI and can be used in Android as well as in standalone kotlin apps.

Usually you inject dependencies like this:

class SplashScreenActivity : Activity() {

    val sampleClass : SampleClass by inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }
}

with the inject() method.

But what about injecting stuff in places where Activity context is not available i.e. outside of an Activity?

3 Answers3

74

There is the KoinComponent which comes to the rescue. In any class you can simply:

class SampleClass : KoinComponent {

    val a : A? by inject()
    val b : B? by inject()
}

Extending KoinComponent gives you access to inject() method.

Remember that usually it's enough to inject stuff the usual way:

class SampleClass(val a : A?, val b: B?)
  • are you sure that your A and B classes can have null values? – Andrey Danilov Apr 26 '18 at 13:59
  • Hmm, I think that from the KISS perspective it is better to use constructor (not to over-engineer thing ;)) as an injection point in such cases. But it is interesting that we can extend `Koin component` to allow injection using Koin. – Patryk Jabłoński Jul 10 '18 at 20:46
  • 1
    Are you saying we don't need `startKoin` in this case? – IgorGanapolsky Dec 28 '18 at 21:03
  • Be aware that KoinComponent should not be used if not strictly necessary: "The other thing I’ve seen around is the misuse of KoinComponent interface to extend service locator API. Be aware that as a Koin framework user, you shouldn’t need to use this interface. " https://medium.com/koin-developers/whats-next-with-koin-2-2-3-0-releases-6c5464ae5e3d – laim2003 Dec 22 '20 at 17:45
24

Koin provides a solution for this using the KoinComponent interface. For example, if you need to get some dependencies in your repository then you can simply implement the KoinComponent interface. It gives you access to various Koin features such as get() and inject(). Use KoinComponent only when you can't rewrite the constructor to accept dependencies as constructor parameters.

class MyRepository: Repository(), KoinComponent {
  private val myService by inject<MyService>()
}

Constructor injection is better than this approach.

For example, the same thing can be achieved by:

class MyRepository(private val service: MyService): Repository() {
    ...
}

And you can add the definition for instantiating this class in a koin module:

val serviceModule = module {
    ...

    factory { MyService() }
}
val repositoryModule = module {
    ...

    factory { MyRepository(get<MyService>()) }
}
harold_admin
  • 1,117
  • 2
  • 10
  • 20
  • Why is constructor injection better than that approach with KoinComponent? – head01 Nov 20 '19 at 14:59
  • 8
    The approach with KoinComponent is known as Field injection. Here are a few reasons why constructor injection is better: 1. Simpler: Your class only care about what dependencies it needs, not about who satisfies them. 2. More Robust: An instance of your class in a consistent state as soon as it is constructed, rather than after the field injection is complete. 3. Easier testing: Declaring dependencies in the constructor makes it easier to supply their mocks while testing. – harold_admin Nov 21 '19 at 05:54
  • @harold_admin nice answer, can you please explain what is "Repository()" that we extend/implement here? – Richard Miller Dec 27 '19 at 21:17
  • @Hatzil It could be any base class. Honestly it doesn't really matter for the purpose of this example. I just wanted to illustrate that you can use Koin whether you have a base class or not. – harold_admin Dec 29 '19 at 16:06
  • Hey @harold_admin good answer, still have a question that how about if the "MyRepository" is a Manifest-declared broadcast receivers? I've solve this by using koin inject extension but maybe there is another solution? – ininmm Jun 15 '20 at 01:21
  • @ininmm I'm not sure if I understand correctly. Is "MyRepository" a broadcast receiver, or does it depend on a broadcast receiver? In the first case, I don't think you can use constructor injection because BroadcastReceivers are instantiated by the system, leaving field injection as the only option. – harold_admin Jun 15 '20 at 08:16
  • Yes I mean that "MyRepository" is derived from a broadcast receiver. You absolutely right, using field injection is the only silver bullet. – ininmm Jun 15 '20 at 14:40
11

If you don't want to implement any interfaces then just take a look at how KoinComponent.inject() is implemented and do something similar yourself:

val foo by lazy { KoinPlatformTools.defaultContext().get().get<FooClass>() }
Mark
  • 7,446
  • 5
  • 55
  • 75