There are two modules in my android project, app module and lib module.
Both these two modules need Koin for D.I., so I call startKoin in MyApplication class in app module, and IninKointContentProvider in lib module as below.
// app module
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin(this, modules1)
}
}
// lib module
class InitKoinContentProvider : ContentProvider() {
override fun onCreate(): Boolean {
startKoin(context.applicationContext, modules2)
return true
}
}
Then app crashed and shown this message
Caused by: org.koin.error.BeanOverrideException: Try to override definition with Single [class='android.content.Context'], but override is not allowed. Use 'override' option in your definition or module.
I guess startKoin can be called only one time.
The solution I found is merging two koin modules then calling startKoin in MyApplication, but I don't like it. Lib module may be imported by other android project which doesn't use koin, in that case, I think calling startKoin in InitKoinContentProvider is better.
Any solution for this problem?? Thanks!
I found the best solution inspired by #laalto's answer, thanks!
Upgrade to koin 2.0, then use KoinApplication and customized KoinComponent to create a isolated koin context, it can let lib module using koin without any initializing call by app module, still start koin in ContentProvider. The whole code may like below.
// app module
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this#MyApplication)
modules(module{
viewModel { MainViewModel() }
})
}
}
}
class MainActivity: AppCompactActivity() {
private val viewModel: MainViewModel by viewModel()
}
// lib module
internal object MyKoinContext {
lateinit var koinApplication: KoinApplication
}
interface MyKoinComponent : KoinComponent {
override fun getKoin(): Koin {
return MyKoinContext.koinApplication.koin
}
}
class InitKoinContentProvider : ContentProvider() {
override fun onCreate(): Boolean {
MyKoinContext.koinApplication = koinApplication {
androidContext(context.applicationContext)
modules(module{
viewModel { FooViewModel() }
})
}
return true
}
}
class FooActivity: AppCompactActivity(), MyKoinComponent {
private val viewModel: FooViewModel by viewModel()
}
Ref:
https://insert-koin.io/docs/2.0/documentation/reference/index.html#_koin_context_isolation
In your library modules, use loadKoinModules() to load the module-specific koin modules. Docs.
You need to have run startKoin() prior to that, so the init order with content providers can be a little tricky.
To init extra koin modules on other project modules and get no duplicate loading issues (e.g. pressing home, than coming back to the activity), go to your module declaration file:
val myModule = module {
single { MyRepository(get()) }
viewModel { MyViewModel(get()) }
}
private val loadKoinModules by lazy {
loadKoinModules(myModule)
}
fun inject() = loadKoinModules
Then on your view:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
inject()
}
TL;DR
Use single/factory methods with override param set to true when providing your dependencies that are overriding those provided by the modules loaded before.
single<Manager>(override = true) { TestManager() }
I have faced a similar issue when I tried to override one of the dependencies for UI test purposes.
When I setup in Application.onCreate():
startKoin {
module {
single { Printer() }
}
}
and then in before method of test:
loadKoinModules(module {
single<Printer> { TestPrinter() }
})
I get a Runtime exception during the test:
org.koin.core.error.DefinitionOverrideException: Already existing definition or try to override an existing one
And the solution is to show Koin that you are intentionally overriding that dependency by using override param of single function like that:
loadKoinModules(module {
single<Printer>(override = true) { TestPrinter() }
})
By design startKoin is meant to be called from the Application class. You can provide a parameter in the lib whether to call startKoin or not. But I doubt that including such things as Koin in libs is a good practice. What if an application already includes Koin, but of different version?
This way worked perfect for me:
#KoinExperimentalAPI
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
initKoin()
}
private fun initKoin() {
startKoin {
androidLogger()
androidContext(this#MainApplication)
}
loadKoinModules(
myFeatureModule
)
}
}
And define you module in your feature as usual:
val myFeatureModule = module {
factory<...> { ...() }
single { ...() }
viewModel { ...(get() }
}
Related
I'm working on Android library that other apps will use it.
This library will not have any activity, but it will have Fragments, VM, domain etc.
So far on my apps i worked with Dagger2, and i'm not sure how it will work in library.
Anyone have experience with it? or maybe someone can recommend other library to use for that case (koin?)?
Thanks
Koin is far more easy to use. You can also get rid of annotations and their handling. Suppose we have a class name Helper and needs to be access from different locations.
Implementation Steps:
a) Add Dependency in build.gradle(app).
implementation "io.insert-koin:koin-android:3.3.0"
b) Create a class extend it with KoinComponent
class DIComponent : KoinComponent {
// Utils
val helper by inject<Helper>()
}
c) Initialize Koin by passing it modules in App class
class MainApplication : Application(){
override fun onCreate() {
super.onCreate()
startKoin{
androidLogger()
androidContext(this#MainApplication)
modules(appModule)
}
}
private val appModule = module {
single { Helper() }
}
}
d) Now, to use this class in project (activity/fragment)
class MainActivity : AppCompatActivity() {
private val diComponent = DIComponent()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
diComponent.helper.yourFunction()
}
}
I have a single activity application.
My MainActivity is referenced in a number of dependency injection modules, as the implementer of these interfaces. I currently have a work around, which is less than ideal.
class MainActivity : TransaktActivity(), RegistrationNavigator, IAuthPresenter,
IAuthButtonNavigator {
override fun navigateAwayFromAuth() {
navController.navigate(R.id.homeFragment)
}
override fun navigateToAuthPin(buttonId: Int) {
//todo navigate to auth with pin fragment
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_mainActivity = this
setContentView(R.layout.activity_main)
}
companion object {
private var _mainActivity: MainActivity? = null
fun getInstance() = _mainActivity
}
}
interface RegistrationNavigator {
fun navigateToCardDetails()
fun navigateToOtpCapture()
fun navigateToLoading()
fun navigateOutOfCardRegistration()
}
The appModule is a Koin Module
val appModule = module {
viewModel { SharedViewModel() }
single { MainActivity.getInstance() as RegistrationNavigator }
}
What is the preferred way of achieving this?
Android-lifecycled components such as activities should not be in koin modules.
For example you will have issues with e.g. configuration changes since the koin module would be serving references to stale activity after the activity is recreated.
I haven't really worked with NavController but rather rolled up my own navigation solution. As a generic approach I would refactor the RegistrationNavigator implementation to a separate class the instance of which you can provide from your koin module. If lifecycle-dependent params such as Context (or NavController) are needed, supply them as function args.
Does Koin provide the functionality of binding several dependencies into a collection, as Dagger does with multibindings?
Let's suppose I have this interface:
interface Initializer: (Application) -> Unit
And this interface has several implementations, for instance:
class LoggingInitializer: Initializer {
override fun invoke(p1: Application) {
Timber.plant(Timber.DebugTree())
}
}
The implementations are provided in different modules using bind modifier:
val coreToolsModules = module {
single { LoggingInitializer() } bind Initializer::class
}
And such modules are installed in the app's Application class:
class TestApplication: Application() {
override fun onCreate() {
super.onCreate()
val startKoin = startKoin {
logger(PrintLogger())
androidContext(this#TestApplication)
modules(listOf(coreToolsModules))
}
}
}
I'd like to inject all implementations of Initializer class as a set to my Application class in order to carry out such initialization:
val initializers: Set<Initializer> by inject()
//in onCreate()
initializers.forEach { it.invoke(this) }
I'm trying to learn Koin for dependency injection in android. I started to follow the example and try to inject very simple object by but I'm getting the error as NoBeanDefFoundException: No definition found for ...
here's my code
Gradle
// Koin
def koin_version = '2.0.1'
implementation "org.koin:koin-androidx-scope:$koin_version"
implementation "org.koin:koin-androidx-viewmodel:$koin_version"
implementation "org.koin:koin-androidx-ext:$koin_version"
Application onCreate()
override fun onCreate() {
super.onCreate()
startKoin{
androidLogger()
androidContext(this#Application)
listOf(applicationModule)
}
}
Modules.kt
val applicationModule = module {
factory { UserSession("email","password") }
}
but when I try to inject it in anywhere (Application, Activity, Fragment) as private val userSession: UserSession by inject() I get above mentioned error. Am I missing something?
You probably got confused by the syntax, you're supposed to call the method modules and provide it with the modules you want started.
The listOf return value is ignored in your case, you're supposed to do something like this:
startKoin {
androidLogger()
androidContext(this#Application)
modules(applicationModule)
}
Reference
try this.
KoinApp.kt
class KoinApp : MultiDexApplication() {
override fun onCreate() {
super.onCreate()
startKoin(this, listOf(appModule))
}
}
appModule.kt
#JvmField
val appModule = module {
single { DataRepository(get()) }
}
The answer will work, but for future proofing, I would still have the list of.
startKoin {
androidLogger()
androidContext(this#Application)
modules(listOf(applicationModule))
}
Modules.kt:
val appModule = module {
single { MyRepositoryImpl() }
}
val viewModelModule = module {
viewModel { MainViewModel(get()) }
}
MyApp.kt:
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this#MyApp)
modules(listOf(appModule, viewModelModule))
}
}
}
Go to AndroidManifest and add:
<application
android:name=".MyApp"
...
</application>
MainActivity.kt:
//ViewModel
private val vm:MainViewModel = get()
I'm trying to make some tests and i need to replace the real dependency by a fake one by overriding it on KODEIN but it's not working and i don't know what i can do anymore.
Here is my dependency graph (I'm omitting others dependencies):
class Injector(private val context: Context) {
val dependencies = Kodein.lazy {
.
.
bind<RetrieveContacts>() with provider {
Log.i("RetrieveContacts","REAL")
RetrieveContactsInMemory()
}
.
.
}
}
Here is my application class:
class AppApplication : Application(), KodeinAware {
override val kodein by Injector(this).dependencies
}
Here is what i'm doing to override the dependency:
#RunWith(value = AndroidJUnit4::class)
class HomeActivityEmptyStateTest {
#get:Rule
var mActivityTestRule = ActivityTestRule<HomeActivity>(HomeActivity::class.java)
#Before
fun setup() {
val appApplication = InstrumentationRegistry.getInstrumentation().targetContext
val kodein by Kodein.lazy {
extend(appApplication.appKodein())
bind<RetrieveContacts>(overrides = true) with provider {
Log.i("RetrieveContacts","FAKE")
RetrieveEmptyContacts()
}
}
}
#Test
fun testWhenHasNoContent() {
...
}
}
I'm still seeing "RetrieveContacts REAL" instead of "RetrieveContacts FAKE" in console log
It seems like you forgot to allow overrides when extending in your test.
extend(appApplication.appKodein(), allowOverride = true)
This does not work since the parents definition will not be overridden. more info: https://kodein.org/Kodein-DI/?6.1/core#_overridden_access_from_parent
This is because Bar is bound to a singleton, the first access would
define the container used (parent or child). If the singleton were
initialized by child, then a subsequent access from parent would yeild
a Bar with a reference to a Foo2, which is not supposed to exist in
parent.