I'm using MVVM architecture for my Android app and just started with Koin. When I'm trying to inject more than one parameter problems start appearing.
For now I have Repository class which uses:
RESTApi to perform web calls. RestProvider uses SocketProvider as constructor parameter for this
Utils(Context) as helper to retrieve some basic information(appVersion,
IMEI etc)
My appModule
{
single<RepositoryApi> {
Repository(
Utils(androidContext())
\\ RestProvider(SocketProvider()) Here I get problems
)
}
single<RestApi> { RestProvider(get() as SocketProvider) }
single<SocketApi> { SocketProvider() }
single<UtilsApi> { Utils(androidContext()) }
viewModel { LoginViewModel(get()) }
}
When I use Utils only everything works fine, when I add RestProvider I get exception:
Caused by: org.koin.core.error.InstanceCreationException: Could not
create instance for
[type:Single,primary_type:'com.etrans.ntsdriver.provider.repository.RepositoryApi']
at org.koin.core.instance.DefinitionInstance.create(DefinitionInstance.kt:61)
at org.koin.core.instance.SingleDefinitionInstance.get(SingleDefinitionInstance.kt:40)
at org.koin.core.definition.BeanDefinition.resolveInstance(BeanDefinition.kt:70)
at org.koin.core.scope.Scope.resolveInstance(Scope.kt:165)
at org.koin.core.scope.Sc
I understand that I'm missing something but I didn't find any tutorials or examples to explain such cases.
My gradle dependencies related to Koin(maybe will be useful):
// Koin for Android - ViewModel features
implementation "org.koin:koin-android-viewmodel:$koin_version"
implementation "org.koin:koin-java:$koin_version"
testImplementation "org.koin:koin-test:$koin_version"
androidTestImplementation "org.koin:koin-test:$koin_version"
Thanks in advance for any help
Here is an example of using Koin to setup retrofit.
private val networkModule = module {
single {
HttpLoggingInterceptor(
HttpLoggingInterceptor.Logger { message ->
//Logger.d("NETWORK: $message")
}).apply {
level = HttpLoggingInterceptor.Level.NONE
}
}
single {
DefaultHeadersInterceptor()
}
single {
OkHttpClient.Builder()
.addInterceptor(get<HttpLoggingInterceptor>())
.addInterceptor(get<DefaultHeadersInterceptor>())
.build()
}
single {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(JacksonConverterFactory.create(ObjectMapper().registerKotlinModule()))
.client(get())
.build()
}
single { get<Retrofit>().create(ApiInterface::class.java) }
}
So on this way you can configure ApiInterface
single { get().create(ApiInterface::class.java) }
Hope this will help.
Related
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())
}
}
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.
Is there any way to implement Dagger's SubCompoent concept on Koin?
The thing that i want to do is using instance from parent scope's one.
app_modules.kt
val favoriteModule = module {
scope(named<FavoriteFragment>()) {
scoped { GetFavoriteMovies(get()) }
scoped { FavoriteVMFactory(get(), get()) } // This need 'MovieEntityMovieMapper'
}
}
val popularModule = module {
scope(named<PopularFragment>()) {
scoped { GetPopularMovies(get()) }
scoped { PopularVMFactory(get(), get()) } // This need 'MovieEntityMovieMapper'
}
}
val searchModule = module {
scope(named<SearchFragment>()) {
scoped { SearchMovies(get()) }
scoped { SearchVMFactory(get(), get()) } // This need 'MovieEntityMovieMapper'
}
}
val mainModule = module {
scope(named<MainActivity>()) {
scoped { MovieEntityMovieMapper() }
// this ImageLoader also injected by Fragments
scoped<ImageLoader> { (activity: Activity) -> GlideImageLoader(activity) }
}
}
Using Dagger, this can be done by SubComponent or Component Dependency.
But in Koin(especially 2.0), i cant not find way.
Some answer said use
GlobalContext.get().koin.getScope("Parent").get<>().
https://github.com/InsertKoinIO/koin/issues/513
Koin sharing instances between modules
but i dont think it's not a clean approach and a dependency injection.
You can try https://github.com/beyama/winter which is inspired by Koin but has child graphs that can be used like Dagger's subcomponents. Winter has been used in production for some major apps with several million installs for a few years now. I hope it is ok to post an example here: https://play.google.com/store/apps/details?id=de.prosiebensat1digital.seventv
Disclaimer: I know the author and have been using his library myself since its release. So far it has been stable in my experience.
I have 2 Retrofit Clients one of them default and second one has different implementation like (base URL, interceptors etc... )
i need to inject default client without using name reference
first client :-
single<Retrofit> {
Retrofit.Builder()
.baseUrl(RemoteConstants.BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(APIResponseConverter())
.addConverterFactory(GsonConverterFactory.create(get()))
.client(get())
.build()
}
Second Client:-
single<Retrofit>("retrofit_second") {
Retrofit.Builder()
.baseUrl("diffrent url")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(get()))
.client(get("SecondOkHttpClient"))
.build()
}
My way to inject
val myModule = module {
factory { get<Retrofit>().create(FirstAPI::class.java) } // Why Koin did not figure it without providing its default name !?
factory { get<Retrofit>("retrofit_second").create(SecondAPI::class.java) }
factory<IMyRemoteDataSource> { MyRemoteDataSource(get(), get()) }
factory<IMyRepository> { MyRepository(get()) }
factory { MyUseCase(get()) }
}
the result is :
Multiple definitions found for type 'class retrofit2.Retrofit' - Koin can't choose between :
Single [name='retrofit_second',class='retrofit2.Retrofit']
Single [name='Retrofit',class='retrofit2.Retrofit']
Why Koin did not get the default Retrofit instance without providing its default name (Retrofit ) !?
factory { get<Retrofit>().create(FirstAPI::class.java) }
You are right #Eslam. Current behavior (as of koin:1.0.2) is when you don't specify the dependency name, it is treated as an empty string. And then the definitions get filtered by class name:
fun searchByClass(clazz: KClass<*>): List<BeanDefinition<*>> {
return definitions.filter { clazz in it.classes }
}
As a result you get both of your definitions, which results in the above mentioned error: Multiple definitions for class ....
I understand how Dagger2 works,
I understand it allows to easily swap dependencies, so we can use mocks for testing.
Point is that I am not sure I understand how am I supposed to provide different Dagger2 Components implementations for testing and for debug/production.
Would I need to create 2 Gradle productFlavors (e.g "Production"/"Test")
that would contain 2 different Components definition?
Or can I specify that I want to use the mock Component for test compile and the non mock Component for non test builds?
I am confused, please some clarification would be great!
Thanks a lot!
Unit testing
Don’t use Dagger for unit testing
For testing a class with #Inject annotated constructor you don't need dagger. Instead create an instance using the constructor with fake or mock dependencies.
final class ThingDoer {
private final ThingGetter getter;
private final ThingPutter putter;
#Inject ThingDoer(ThingGetter getter, ThingPutter putter) {
this.getter = getter;
this.putter = putter;
}
String doTheThing(int howManyTimes) { /* … */ }
}
public class ThingDoerTest {
#Test
public void testDoTheThing() {
ThingDoer doer = new ThingDoer(fakeGetter, fakePutter);
assertEquals("done", doer.doTheThing(5));
}
}
Functional/integration/end-to-end testing
Functional/integration/end-to-end tests typically use the production
application, but substitute fakes[^fakes-not-mocks] for persistence,
backends, and auth systems, leaving the rest of the application to
operate normally. That approach lends itself to having one (or maybe a
small finite number) of test configurations, where the test
configuration replaces some of the bindings in the prod configuration.
You have two options here:
Option 1: Override bindings by subclassing modules
#Component(modules = {AuthModule.class, /* … */})
interface MyApplicationComponent { /* … */ }
#Module
class AuthModule {
#Provides AuthManager authManager(AuthManagerImpl impl) {
return impl;
}
}
class FakeAuthModule extends AuthModule {
#Override
AuthManager authManager(AuthManagerImpl impl) {
return new FakeAuthManager();
}
}
MyApplicationComponent testingComponent = DaggerMyApplicationComponent.builder()
.authModule(new FakeAuthModule())
.build();
Option 2: Separate component configurations
#Component(modules = {
OAuthModule.class, // real auth
FooServiceModule.class, // real backend
OtherApplicationModule.class,
/* … */ })
interface ProductionComponent {
Server server();
}
#Component(modules = {
FakeAuthModule.class, // fake auth
FakeFooServiceModule.class, // fake backend
OtherApplicationModule.class,
/* … */})
interface TestComponent extends ProductionComponent {
FakeAuthManager fakeAuthManager();
FakeFooService fakeFooService();
}
More about it in the official documentation testing page.