Android Koin simple object inject fail - android

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()

Related

declareMock<> not working with mockk in android unit test

i am trying to mock a repository class using declareMock and mockk but it doesn't seem to be working as i am getting the data from the real repository.
Version
implementation "org.koin:koin-androidx-viewmodel:2.1.6"
testImplementation "org.koin:koin-test:2.1.6"
Code
My application class (only relevant parts):
class MyApplication : Application() {
companion object {
val appModule = module {
single<RepositoryUserLists> { RepositoryUserListsImpl() }
}
}
override fun onCreate() {
super.onCreate()
startKoin {
androidLogger()
androidContext(this#MyApplication)
modules(appModule)
}
}
}
My test class (only relevant parts)
class TestRepositoryUserLists : KoinTest {
#get:Rule
val koinTestRule = KoinTestRule.create {
modules(MyApplication.appModule)
}
#get:Rule
val mockProvider = MockProviderRule.create { clazz ->
mockkClass(clazz.java.kotlin)
}
#Rule
#JvmField
val instantExecutorRule = InstantTaskExecutorRule()
private val repo: RepositoryUserLists by inject()
#Before
fun before() {
declareMock<RepositoryUserLists> {
every { getAllLists() } returns MutableLiveData(listOf(MyList("test list")))
}
//PROBLEM IS HERE
//Expected: a list containing one item names "test list".
//Actual: empty list (like in real repository).
repo.getAllLists().value
}
}
github issue: https://github.com/InsertKoinIO/koin/issues/841
Just posted it here for those migrating to a newer version of the koin like 2.2.2,
and the dependency test was failed with an error Missing MockProvider. Please use MockProvider.register() to register a new mock provider.
import io.mockk.every
import io.mockk.mockkClass
...
val app = koinApplication {
printLogger(Level.DEBUG)
modules(...)
}
MockProvider.register { mockkClass(it) }
with(app.koin) {
declareMock<MyService> {
every { someAction() } returns "some answer"
...
}
}
app.checkModules()
I think you need to use the given/will syntax inside the declareMock block
declareMock<RepositoryUserLists> {
given(getAllLists()).willReturn(MutableLiveData(listOf(MyList("test list"))))
}

How fix No definition found for class:'android.content.Context'. Check your definitions! in KoinTest

In my KoinTest extends
#Before
fun setUp() {
startKoin { modules(KoinStarter.getModules()) }
}
#Test
fun `should inject my components`() {
val settingsStore: SettingsStore = get()
assertNotNull(settingsStore)
}
I get error No definition found for class:'android.content.Context'. Check your definitions!
But my module located in KoinStarter.getModules()
val localDataModule = module {
factory<Resources> { get<Context>().resources }
single<SettingsStore> { SettingsStore(get<Context>()) }
}
You need to provide the application context in the androidContext() method.
Since your question is about testing specifically, in my case I had to pass application context via ApplicationProvider:
startKoin {
androidContext(ApplicationProvider.getApplicationContext())
.....
}
Don't forget to add the androidx dependency:
testImplementation 'androidx.test:core:1.0.0'

Dagger2 Provider in koin

Is there any alternative to javax.inject.Provider in koin?
To react to actions, I am injecting Commands to my activity.
Command is a single-run object, for example WriteToFile.
In dagger I could make it like this:
class MainPresenter : Presenter() {
#Inject
lateinit var writeFile: Provider<WriteFileCommand>
fun onSaveClicked() {
writeFile.get().run()
}
}
in koin, when I try to use:
class MainPresenter : Presenter() {
lateinit var writeFile: Provider<WriteFileCommand> by inject()
fun onSaveClicked() {
writeFile.get().run()
}
}
My koin module:
val appModule = module {
factory { WriteFileCommand(get(), get()) }
factory { FileProvider() }
single { DataStore() }
}
Than I got error saying:
Can't create definition for 'Factory [name='WriteFileCommand',class='com.test.WriteFileCommand']' due to error :
No compatible definition found. Check your module definition
I understand that I can call:
var command: WriteFileCommand = StandAloneContext.getKoin().koinContext.get()
command.run()
But It looks so cumbersome
There's nothing like a provider directly. If you use inject, you'll use a lazy delegate. If you use get, you'll create a new instance you declared the dependency with a factory. So get is what you need in your case. Just let your MainPresenter implement KoinComponent and you'll be able to use get directly:
class MainPresenter : Presenter(), KoinCompontent {
fun onSaveClicked() = get<WriteFileCommand>().run()
}

Koin analogue to Dagger IntoSet

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) }

How to use Koin in multiple module?

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() }
}

Categories

Resources