Koin Scope and Interface - android

I am using Koin di library in my project. Version of lib is 1.0.0-RC-1.
My module:
val appModule = module {
scope("UserScope") { UserToaster(androidContext()) as Toaster }
scope("AnonScope") { AnonToaster(androidContext()) as Toaster }
}
I started koin in my Application class and created scope:
override fun onCreate() {
super.onCreate()
startKoin(this, listOf(appModule))
getKoin().getOrCreateScope("AnonScope")
}
And next I tried to inject implementation of Toaster from current scope to variable in Activity. Here the code:
private val toaster: Toaster by inject(scope = "AnonScope")
After this I got an error:
Caused by: org.koin.error.DependencyResolutionException: Multiple definitions found for type 'interface com.example.nkirilov.playground.Toaster (Kotlin reflection is not available)' - Koin can't choose between :
Scope [name='UserScope',class='com.example.nkirilov.playground.Toaster']
Scope [name='AnonScope',class='com.example.nkirilov.playground.Toaster']
Check your modules definition, use inner modules visibility or definition names.
I do not understand why this does not work (If use single with different names - it will work). Is that koin bug? How to avoid this error?

I implemented it like this
Module:
val navigationModule = module {
scope(DI.APP_SCOPE) { ClassA().create }
scope(DI.APP_SCOPE) { get(scopeId = DI.APP_SCOPE).classB }
scope(DI.APP_SCOPE) { get(scopeId = DI.APP_SCOPE).classC }
}
val authModule = module {
scope(DI.AUTH_SCOPE) { ClassA.create(ChildClassB(get(scopeId = DI.APP_SCOPE))) }
scope(DI.AUTH_SCOPE) { get(scopeId = DI.AUTH_SCOPE).classB }
scope(DI.AUTH_SCOPE) { get(scopeId = DI.AUTH_SCOPE).classC }
}
Main Activity:
private val classC: ClassC by inject(scope = getKoin().getOrCreateScope(APP_SCOPE))
AuthActivity:
private val classC: ClassC by inject(scope = getKoin().getOrCreateScope(DI.AUTH_SCOPE))

your definitions have the same name in Koin. Current version (~1.0.*) avoid you to specify which scope to use, by automating resolving a type and it's session id.
Can you avoid describe your definitions with same type, like:
val appModule = module {
scope("UserScope") { UserToaster(androidContext()) }
scope("AnonScope") { AnonToaster(androidContext()) }
}
Else we would need a feature to specify which scope to use when resolving a type. You would resolve it with:
val userScope = getScope("UserScope")
get<Toaster>(scope = userScope)

Related

Koin dependency Injection Isssue - Android

In my android application am using MVVM architecture and using koin library for DI.
Below is my Repository class:
class JaiminRepository constructor(
private var remoteDataSource : RemoteDataSource,
private var enrollApiInterface : EnrollApiInterface
) {
...
}
Created module for is as below:
val jaiminRepositoryModule = module {
single {
JaiminRepository(get(),get())
}
}
For this I am getting error as :
Instance creation error : could not create instance for
[Singleton:'com.jaimin.sdk.repository.JaiminRepository']:
org.koin.core.error.NoBeanDefFoundException: |- No definition found
for class:'com.jaimin.api.RemoteDataSource'. Check your definitions!
org.koin.core.scope.Scope.throwDefinitionNotFound(Scope.kt:287)
org.koin.core.scope.Scope.resolveValue(Scope.kt:257)
So I have added factory for RemoteDataSource.
factory {
RemoteDataSource()
}
and finally it look like as below:
val jaiminRepositoryModule = module {
factory {
RemoteDataSource()
}
single {
JaiminRepository(get(),get())
}
}
But still am getting error. What might be the issue? Do I need to do something with EnrollApiInterface also? Please guide. Thanks in Advance.
You have to define all dependencies in the module(or another module), otherwise your repository can't be created. Make sure you also provide the EnrollApiInterface:
val jaiminRepositoryModule = module {
factory<EnrollApiInterface> {
EnrollApiInterfaceImpl()
}
factory {
RemoteDataSource()
}
single {
JaiminRepository(get(),get())
}
}

Android KOIN CRASH - Error Caused by: org.koin.core.error.DefinitionOverrideException: Already existing definition for [Singleton:'java.lang.String'

After updated koin and gradle, the following error prompt
Caused by: org.koin.core.error.DefinitionOverrideException: Already existing definition for [Singleton:'java.lang.String'] at java.lang.String::_root_
I don't know where is the cause of this error.
Here my Application files:
Class MyApplication --> With 2 modules, I'm importing startKoin from: import org.koin.core.context.GlobalContext.startKoin
class MyApplication : Application(), OnMapsSdkInitializedCallback {
override fun onCreate() {
super.onCreate()
startKoin{
if(BuildConfig.DEBUG){
androidLogger(Level.DEBUG)
Timber.plant(Timber.DebugTree())
}
androidContext(this#MyApplication)
modules(applicationModule, viewModelModule)
}
MapsInitializer.initialize(applicationContext, Renderer.LATEST, this)
}
override fun onMapsSdkInitialized(renderer: Renderer) {
when (renderer) {
Renderer.LATEST -> Timber.d("The latest version of the google maps renderer is used.")
Renderer.LEGACY -> Timber.d("The legacy version of the google maps renderer is used.")
}
}
}
MODULE 1 -- applicationModule
// declare a module
val applicationModule = module {
single { BuildConfig.SOME_STRING1 }
single { BuildConfig.SOME_STRING2 }
single { BuildConfig.SOME_STRING3 }
single { BuildConfig.SOME_STRING4 }
single {
Environment(get(named("SOME_STRING1")),
get(named("SOME_STRING2")),
get(named("SOME_STRING3")),
get(named("SOME_STRING4")), get())
} bind GrpcConfiguration::class
//endregion
//region Channel
single {
GrpcChannelBuilder.Companion.prepare(get(), get())
}
//endregion
//region Repository
single { SomeDataRepository(get()) } bind SomeRepository::class
//endregion
//region XXXXProvider
single { XXXXProvider(get()) }
//endregion
//region REST API
single (named("RETROFIT")){
Retrofit.Builder().client(get())
.baseUrl("xxxxxxxxxx")
.addConverterFactory(GsonConverterFactory.create())
.build()
} bind Retrofit::class
//region REST API
single {
val l = HttpLoggingInterceptor()
l.level = HttpLoggingInterceptor.Level.BODY
OkHttpClient.Builder().addInterceptor(l).build()
}
single {
get<Retrofit>(named("RETROFIT")).create(XXXXService::class.java)
} bind XXXXService::class
//endregion
//region DB
single {
MyAppDatabase.getDatabase(get()).someDao()
} bind SomeDao::class
//endregion
//region Util
single {
DarkModeUtil()
} bind DarkModeUtil::class
//endregion
}
Module 2 -- viewModelModule (only for viewmodels)
// declare a module
val viewModelModule: Module = module {
viewModel { HomeViewModel(get(), get(), get()) }
}
build.gradle(:app) KOIN dependencies:
def koin_version = "3.1.6" (I've tried with 3.2.2 and 3.2.1 and got the same error)
// Koin main features for Android
implementation "io.insert-koin:koin-android:$koin_version"
// No more koin-android-viewmodel, koin-android-scope, koin-android-fragment
// Java Compatibility
implementation "io.insert-koin:koin-android-compat:$koin_version"
// Jetpack WorkManager
implementation "io.insert-koin:koin-androidx-workmanager:$koin_version"
// Navigation Graph
implementation "io.insert-koin:koin-androidx-navigation:$koin_version"
ANDROID GRADLE PLUGIN VERSION: 7.2.2
GRADLE VERSION: 7.5
KOTLIN VERSION: 1.7.10
Caused by: org.koin.core.error.DefinitionOverrideException:
Already existing definition for [Singleton:'java.lang.String'] at java.lang.String::_root_
The error indicates that there are many String type definitions without a qualifier.
These lines below are causing the problem.
single { BuildConfig.SOME_STRING1 }
single { BuildConfig.SOME_STRING2 }
If you would like to declare a definition with the same type, you can declare them by giving a name.
single(named("SOME_STRING1")) { BuildConfig.SOME_STRING1 }
single(named("SOME_STRING2")) { BuildConfig.SOME_STRING2 }
single(named("SOME_STRING3")) { BuildConfig.SOME_STRING3 }
single(named("SOME_STRING4")) { BuildConfig.SOME_STRING4 }
You already use named definitions to create an Environment object.
single {
Environment(get(named("SOME_STRING1")),
get(named("SOME_STRING2")), ...
Hope this helps.

Single instance of firebase

How to get single instance of firebase in android throughout application.
Iam getting instance from class.whlie trying to do from module level am getting fail.
Class firebasemodule{
Var module = module{
singlee(createdatstart=true){
gett<Firebase>().instance}}
}
In app level class
Startkoin{ module(listof(firebasemodule))}
Here am getting error.
Any suggestion accepted and I would be helpful for me and for many also.
You should use
class FirebaseModule {
val module = module {
single {
FirebaseFirestore.getInstance()
}
}
}
If don't want to take instance in this module , we can create seperate
class for firebase and call that class in module.
class FirebaseModule {
val module = module {
single {
FirebaseHandler()
}
}
}
class FirebaseHandler(){
val firebase = Firebase.getInstance()
}

findNavController with a DI tool

I have a single activity and multiple fragments styled application using the navigation component.
I am using Koin for my DI. I was wanting to create a Navigator class in my application as per the postulates of clean architecture.
This hypothetical class would look like :
class Navigator(private val navHostFragment: NavHostFragment)
{
fun toStudentsProfile():Unit
{
val action = HomeFragmentDirections.toStudentsProfile()
navHostFragment.findNavController().navigate(action)
}
fun toTeachersProfile():Unit
{
val action = HomeFragmentDirections.toTeachersProfile()
navHostFragment.findNavController().navigate(action)
}
}
My problem now is how should I create this under the Koin container ?
val platformModule = module {
single { Navigator("WHAT CAN BE DONE HERE") }
single { Session(get()) }
single { CoroutineScope(Dispatchers.IO + Job()) }
}
Furthermore, the Koin component would get ready before the navhostfragment is ready hence it won't be able to satisfy the dependency, to begin with.
Is there a way to provide Koin with an instance of a class and then subsequently start using it?
Koin allows to use parameters on injection
val platformModule = module {
factory { (navHostFragment: NavHostFragment) -> Navigator(navHostFragment) }
single { Session(get()) }
single { CoroutineScope(Dispatchers.IO + Job()) }
}
I have declared the dependency as factory, i guess it could be scoped to the activity as well. Declaring it as single will lead to misbehavior, as if the activity (therefore the navhostFragment) is re-created, the Navigator object will be referencing the destroyed navhostFragment.
As the fragments will be navhostFragment children, you can obtain the Navigator object in the fragments this way:
val navigator: Navigator by inject { parametersOf(requireParentFragment()) }

How to provide parameters in Koin dry run test?

My ViewModel needs repository & genre through constructor. repository is provided by Koin & genre string is provided from activity
// Main app module
val MovieListModule: Module = applicationContext {
// provide repository
bean {
DummyMovieListRepository() as MovieListRepository
}
// provides ViewModel
viewModel { params: ParameterProvider ->
MovieListViewModel(respository = get(), genre = params["key.genre"])
}
}
//Module list for startKoin()
val appModules = listOf(MovieListModule)
//in activity
val viewModel = getViewModel<MovieListViewModel> {
mapOf("key.genre" to "Action / Drama")
}
// dry run test which fails
class KoinDryRunTest : KoinTest {
#Test
fun dependencyGraphDryRun() {
startKoin(list = appModules)
dryRun()
}
}
// some error log
org.koin.error.MissingParameterException: Parameter 'key.genre' is missing
at org.koin.dsl.context.ParameterHolder.get(ParameterHolder.kt:46)
org.koin.error.BeanInstanceCreationException: Can't create bean Factory[class=io.github.karadkar.popularmovies.MovieListViewModel, binds~(android.arch.lifecycle.ViewModel)] due to error :
org.koin.error.MissingParameterException: Parameter 'key.genre' is missing
here Koin (v 0.9.3) injection inactivity works as expected but the dry run test fails as it can't find parameter key.genre. Check full error-log
Is there any way to mock/provide key.genre value to dry run test?
full app source
as Arnaud Giuliani pointed on twitter. dryRun accepts lambda function for parameters
class KoinDryRunTest : KoinTest {
#Test
fun dependencyGraphDryRun() {
startKoin(list = appModules)
dryRun() {
mapOf("key.genre" to "dummy string")
}
}
}

Categories

Resources