https://kotlinlang.org logo
#koin
Title
# koin
l

Lilly

02/19/2023, 10:01 PM
I have a general question regarding
multi-module project
and utilizing a
DI library
. In particular I think about KOIN as DI library. You have to instruct your application somehow to load all koin modules (not gradle modules) to build the dependency graph. One option to achieve this, is to create a separat gradle module, lets call it di which is aware of all other gradle modules, so the app module can simply depend on it and can load the koin modules from there. Further, I came up with idea to put all koin modules in di gradle module rather than let every gradle module provide its koin module. The advantage is that all other gradle modules are independent of a DI library but the disadvantage is that almost every class of the other gradle modules has to be public so that di module is aware of the references. My question is: which approach would you choose and why?
p

Pedro Alberto

02/20/2023, 8:37 AM
What we usually do is that for each feature we have a publicMyFutureModule in there, you only have interfaces these interfaces are the one we use to access something from other FeatureModule
l

Lilly

02/20/2023, 10:13 AM
@Pedro Alberto but having an interface also requires to have everything public that needs instantiation. Do you have a public showcase project maybe?
p

Pedro Alberto

02/20/2023, 10:20 AM
nope 😞 I don't have but no you don't need everything public so what what have is something like this:
Copy code
val myAppModule = module {
    single<MYPublicInterface> {
       MyPrivateImpl()
    }
}
then in the gradle module I only depend on the one that has the Interface the koin module is built internally the feature module has always a shared module
l

Lilly

02/20/2023, 10:33 AM
I can't imagine what your structure looks like but I'm pretty sure I'm looking for something else. I want to move the koin modules from my gradle modules into a single gradle module which I call di. But this gradle module needs to know the classes of all other gradle modules...ergo every class that has to be instantiated must be public
p

Pedro Alberto

02/20/2023, 10:33 AM
But then this module depends on all modules
l

Lilly

02/20/2023, 10:34 AM
Thats true and intended
p

Pedro Alberto

02/20/2023, 10:34 AM
but is bad
cus then in terms of graph if have on module makes your build depend on all modules
l

Lilly

02/20/2023, 10:35 AM
There is always one gradle module which depends on all others
p

Pedro Alberto

02/20/2023, 10:35 AM
🙂 depends on project
l

Lilly

02/20/2023, 10:39 AM
Yeah different approaches exist. I guess I'm fine, thanks for sharing your experience
@Pedro Alberto Just one last question: Do you use context isolation in your feature modules? https://insert-koin.io/docs/reference/koin-core/context-isolation/
p

Pedro Alberto

02/20/2023, 10:45 AM
yes but only on a library we have that is shared btw apps usually thats more for sdks/library
btw you also have an example how @arnaud.giuliani has several modules https://github.com/InsertKoinIO/nowinandroid
is not the way I described it but is also good one in my opinion
a

arnaud.giuliani

02/20/2023, 1:14 PM
in a multi module approach, I would see 2 ways to go • main module and sub modules - main is knowing about all the modules to load at start • load on demand - each part is responsible to load its modules
the 2nd approach ask you to anticipate when you need to call Koin to load a new module
The sample with NowInAndroid is pretty classical sample, monolith example in dagger style
l

Lilly

02/20/2023, 1:23 PM
@arnaud.giuliani cool that you join the discussion. I'm with the first approach. Some odd question came up: Why can be a class internal but also be known by KOIN to instantiate them? Example: feature gradle module
Copy code
internal class MyClass {}

val myKoinModule {
 singleOf(::MyClass)
}
app gradle module (just for completeness)
Copy code
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidLogger()
            androidContext(this@DcInternApplication)
            modules(appModule, myKoinModule)
        }
}
Dependency direction: app -> feature
a

arnaud.giuliani

02/20/2023, 2:33 PM
in fact Koin can load it, and have it in memory but ... if you try to resolve the type from another module, you won’t be able to use it
you won’t be able to do `by inject<MyClass>()`from elsewhere, if it’s internal
your compilation visibility allow you to use it in internal way only
l

Lilly

02/20/2023, 2:54 PM
Can you please explain why KOIN is able to load it? Is it reflection?
@arnaud.giuliani I can't figure it out how it works. Can you help?
a

arnaud.giuliani

02/21/2023, 4:14 PM
sure 🙂
basically, Koin is loading all the object in memory
then your have the potentiel to request anything from Koin
But
The compilation visibility of your project module (gradle) only show you what you can see.
This is where Dagger tries to deal with visibility from DI side, while your compilation unit is already behind visibility constraint due to Gradle dependency
Koin is loading all the definitions
each feature module has limited visibility due to their dependencies (they don’t see other features for examples)
each feature can request only what they “see”
l

Lilly

02/21/2023, 5:21 PM
@arnaud.giuliani Thanks for your invest. I can follow so far but I still don't understand how it works in my project. I have checked your sample but my project is done differently, it is modularized by layer: Dependency direction: app > domain < proto and app > proto app references public Configurator class in domain. domain:
class Configuratior(private val protocol: Protocol)
Protocol
is a public interface in domain. proto: proto contains implementation of
Protocol
interface
internal class ProtocolImpl() : Protocol
proto also contains:
Copy code
val myKoinModule {
 singleOf(::ProtocolImpl) bind Protocol::class
}
myKoinModule is loaded on
startKoin()
So when
Configurator
object is created,
ProtocolImpl
is created too but how can
ProtocolImpl
be created when visibility of it is
internal
? It is neither visible nor accesible to the outer world but it can still be accessed via Koin somehow which should be impossible under normal circumstances. Even if the creation is done in proto gradle module, how is the object accessed? There must be some secret magic going on. Please enlighten me
@arnaud.giuliani I guess I got it. Is it correct that instantiation of koin module members is done in same gradle module where koin module relies and also kept there in memory like you said?
s

s3rius

02/22/2023, 7:33 PM
Usually you can find out where some a class can be used by only considering your source code (I mean literally the text you write). If you have an internal class, it's visible within the entire module. That means all source code inside the module can access it. It doesn't matter if that source code ends up being called from some other place. What matters is that it's written in the correct module. (This explanation isn't 100% correct, but usually it fits.) Koin doesn't do anything special. The code you wrote that refers to
ProtocolImpl
is in the correct module, so it can be called. If you only use
singleOf(::ProtocolImpl)
(without bind) then that would compile fine and you can use it inside this module. But when you try to get this dependency in a different module, it won't work because you aren't allowed to write
ProtocolImpl
in that other module. You could replace Koin completely with this code and it would work the same:
Copy code
fun getA(): Protocol {
	return ProtocolImpl(...)
}

fun getB(): ProtocolImpl {
	return ProtocolImpl(...)
}
getA can be used anywhere because anyone who calls the function will only see a
Protocol
. getB will not work anywhere outside the same module because anyone who calls it will see a
ProtocolImpl
.
l

Lilly

02/22/2023, 8:55 PM
Yeah I had to make a sample to figure it out. Hard to grasp in a mental "run-through". Anyway, thanks a lot 🙏
p

Pedro Alberto

02/23/2023, 9:01 AM
@s3rius is right that is what I proposed so impl is internal and public is the interface you always need to tell the type you return.
6 Views