Integrate Koin in AAR - android

I have an AAR project, and i'm trying to integrate Koin (my first Koin project).
I'm following Koin instruction for context isolation (https://insert-koin.io/docs/reference/koin-core/context-isolation/). and i will be glad to get some help.
I need to register the koin context this way:
MyKoinContext.koinApp = KoinApp
I don't understand when should i call this line and what is the KoinApp (should be koinApplication()?)
The second part that i'm not sure is that i should configure KoinComponent:
abstract class CustomKoinComponent : KoinComponent {
// Override default Koin instance, initially target on GlobalContext to yours
override fun getKoin(): Koin = MyKoinContext.koinApp?.koin
}
but getKoin() should return Koin and not Koin? how sholud i changed it.

Related

Koin Context Isolation: setup correctly and avoid random NoBeanDefFoundException

I have an Android library module that I package and publish. It's a dependency in different apps, some that use Koin and some that don't, therefore I want to use Koin's context isolation.
Having followed the docs, I added a koin component and context as follows:
internal object LocalKoinContext {
lateinit var koinApplication: KoinApplication
}
// Custom KoinComponent using the local instance & not the Global context
interface CustomKoinComponent : KoinComponent {
// override the used Koin instance to use the local koin application instance (LocalKoinContext.koinApplication)
override fun getKoin(): Koin = LocalKoinContext.koinApplication.koin
}
and add create the koinApplication like this:
// Local Koin application instance
LocalKoinContext.koinApplication = koinApplication {
// use AndroidLogger as Koin Logger - default Level.INFO
androidLogger(Level.ERROR)
// use the Android context given there
androidContext(applicationContext)
// load properties from assets/koin.properties file
androidFileProperties()
// declare used modules
modules(theModule)
}
I have an activity that uses a view model that lives within the local Koin component and I used to instantiate the koinApplication within the onCreate of the that activity:
class FirstActivity : AppCompatActivity(),
CustomKoinComponent {
private val sharedViewModel by viewModel<MySharedViewModel>()
...
override fun onCreate(savedInstanceState: Bundle?) {
...
// Local Koin application instance
LocalKoinContext.koinApplication = koinApplication {
// use AndroidLogger as Koin Logger - default Level.INFO
androidLogger(Level.ERROR)
// use the Android context given there
androidContext(applicationContext)
// load properties from assets/koin.properties file
androidFileProperties()
// declare used modules
modules(theModule)
}
}
The issue is that I get random NoBeanDefFoundException
Caused by org.koin.core.error.NoBeanDefFoundException: No definition found for class:'my.package.name.MySharedViewModel'. Check your definitions!
at org.koin.core.scope.Scope.throwDefinitionNotFound(Scope.java:264)
at org.koin.core.scope.Scope.resolveInstance(Scope.java:233)
at org.koin.core.scope.Scope.get(Scope.java:204)
at org.koin.androidx.viewmodel.factory.DefaultViewModelFactory.create(DefaultViewModelFactory.java:11)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:187)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)
at org.koin.androidx.viewmodel.ViewModelResolverKt.get(ViewModelResolverKt.java:23)
at org.koin.androidx.viewmodel.ViewModelResolverKt.resolveInstance(ViewModelResolverKt.java:12)
at org.koin.androidx.viewmodel.scope.ScopeExtKt.getViewModel(ScopeExtKt.java:86)
at org.koin.androidx.viewmodel.scope.ScopeExtKt.getViewModel(ScopeExtKt.java:72)
at org.koin.androidx.viewmodel.ext.android.ViewModelStoreOwnerExtKt.getViewModel(ViewModelStoreOwnerExtKt.java:68)
at org.koin.androidx.viewmodel.ext.android.ViewModelStoreOwnerExtKt.getViewModel$default(ViewModelStoreOwnerExtKt.java:58)
at my.package.name.FirstActivity$special$$inlined$viewModel$default$1.invoke(FirstActivity.java:75)
I suspect it's because sometimes the koinApplication doesn't have time to set itself up but as it's rare I haven't been able to reproduce the issue.
In the answer to this stack question, the dev creates a ContentProvider to instantiate the koinApplication. I've tried it out and so far so good (although, I will only know in months from now), but is this truly the way to go? Seems to be an overkill to have to create a content provider just for that.
Can anyone shed some light on where we are supposed to instantiate the local KoinApplication?
Can anyone shed some light on where we are supposed to instantiate the local KoinApplication?
You should do this inside your Application level class, for example this is what I have on my Application class onCreate
startKoin {
androidLogger(Level.ERROR)
androidContext(this#VeroApp)
modules(AppModules.modules) <-- list of defined koin modules
}
I'm not so sure about the usage of local KoinApplication interface. I know that AppCompatActivity for example is out of the box supported and you can just do a without extending anything
private val sharedViewModel by viewModel<MySharedViewModel>()

How to start koin from another class instead of Application class

I am creating an app as library. The main app in which library has to be integrated has an Application class, so I can't add Application class in my library app. I have found that koin has to be started from Application class. Can I call startKoin from another class?
You can create your own KoinApplication which does not share the global koin context. You also don't need to initialize this inside an Application class. This instance can then be used in your own implementation of KoinComponent.
You can find a detailed description here:
https://doc.insert-koin.io/#/koin-core/start-koin?id=koin-context-isolation
If you purpose is to inject library dependencies to your app, you can have your library expose set of modules that can be referred from you app and be initialised while starting Koin.
For example:
In library, you have a public module as libraryModule:
val libraryModule = module{
single{
ObjectA()
}
}
Now, when you include your library in your app module, you would be able use it as:
class MyApplication : Application{
override fun onCreate() {
super.onCreate()
initializeKoinDI()
}
private fun initializeKoinDI() {
startKoin {
androidContext(this#MyApplication)
modules(listOf(appModule1, appModule2, libraryModule))
}
}
}

Koin Android Test

I have a problem with Koin & "androidTest".
Because androidTest starts the Application i don't need to start Koin by myself in the test.
Now i need to inject a mock service. The problem is, that i inject inside of a method with get() inside of a singleton class and this is not working via constructor injection because the injected object can have different implementations.
My idea was to declare what i need this way:
declare {
factory<Webservice>(override = true) { mockWebservice }
}
But this will be applied on all tests. That's why an other test, which checks if the correct class was injected failed.
I also tried to use stopKoin(), startKoin(listOf(appModule)) in the #After method, but with this the dependency injection doesn't work anymore in later tests.
Is there a way to declare the mock only for one test?
Here is how I do it in my Android Tests:
In a parent test class, I use these methods for setup and teardown:
#Before fun startKoinForTest() {
if (GlobalContext.getOrNull() == null) {
startKoin {
androidLogger()
androidContext(application)
modules(appComponent)
}
}
}
#After fun stopKoinAfterTest() = stopKoin()
My appcomponent contains all modules needed for the dependency tree.
Then, when I want to mock a dependency for a specific test, I use something like this:
declareMock<TripApi> { given(this.fetch(any())).willReturn(TestData.usaTrip) }
You will need to add a new mock declaration for each test if you wish to swap a dependency with a mock.
To declare mock only for one test you can use loadKoinModules()
You can’t call the startKoin() function more than once. But you can use directly the loadKoinModules() functions.
So this way your definition will override default one
loadKoinModules(module {
factory<Webservice>(override = true) { mockWebservice }
})
Also, don't forget to implement KoinTest interface in you test class

How to mock after setup dagger-android 2.15 when writing espresso tests?

If we just use plain dagger 2. In the application class, we will have a property which holds the AppComponent. Then we can swap it during espresso tests.
But when I setup my project using dagger-android 2.15. Things becomes more implicit if adopt too much Dagger magic. The code is more clean, but makes testing a little bit hard.
This is the application class:
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent
.builder()
.create(this)
.build()
}
}
This is the HomeActivity
class HomeActivity : DaggerAppCompatActivity() {
#Inject
lateinit var userPreference: UserPreference
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
if (!this.userPreference.memberRegistered) {
goToActivity(EntryActivity::class.java)
}
}
}
Take this code for example. How to mock that injected userPreference.memberRegistered Which could be a HTTP call underneath?
For those who is interested in this, I got a blog with step by step detail for this:
Basically, the idea is:
You still generate instances for injection in #Module
But We’ll create new #Component A only for testing
This #Component will have a method to get that #Module
During tests, we swap the #Component that the application use with our component A
Then things are easy:
Without DaggerMock
In the #Module, instead of return real instance, you just return mockito mock.
With DaggerMock
You declare the type you want to swap and mock it
You can then use the mock.
No need to change the #Module
It inspires by #AutonomousApps 's solution, but the differences are now you don't need to write the #Component, #Module for each test class.
After trying several approaches, this is the only one that worked for me.
I wrote a blog post that explains how to do this just yesterday: https://dev.to/autonomousapps/the-daggerandroid-missing-documentation-33kj
I don't intend to repeat the entire post for this answer (it's hundreds of words and lines of code to properly set up a test harness with Dagger), but to attempt to summarize:
Add a custom application class in the debug source set (I assume it would also work in the androidTest source set, but I have not tried this).
You also need to reference this application in a AndroidManifest.xml in the same source set.
Create a "Test component" in your androidTest class that extends from your production top-level component and build it.
Use that test component to inject your application, which means you've just replaced your entire Dagger dependency graph with a new one you've defined just for the test suite.
Profit.

How to inject test overrides into the default dependency graph?

I would like to inject mocked overrides into my Android instrumentation tests using Kodein. I don't know which is the optimal approach to do this. Here's what I have in mind:
My app uses a KodeinAware application class. The served Kodein instance holds all dependencies required by my app.
In my tests I would like to inject mocked overrides for specific dependencies to test behavior of the app in various situations.
Overrides should be different for each test, and should be injected before/while the test runs.
Is the configurable Kodein extension sensible in this situation, or is there a simpler, better suited approach (and if so, which)?
If your test is given a Kodein instance (meaning that it can use a different Kodein object than the one held by your Application), then the recommended approach is to create a new Kodein object that extends the one of the app and overrides all necessary bindings.
val testKodein = Kodein {
extend(appKodein())
bind<MyManager>(overrides = true) with singleton { mock<MyManager>() }
}
The configurable Kodein option is recommended only if you're using a static "one true Kodein". Using it prevents the possibility to run you're tests in parallel (because they all access the same Kodein instance), and forces you to clear the ConfigurableKodein between each tests and re-declare every time different overrides.
I am now using the ConfigurableKodein inside my custom App class.
class App : Application(), KodeinAware {
override val kodein = ConfigurableKodein()
override fun onCreate() {
super.onCreate()
// A function is used to create a Kodein module with all app deps.
kodein.addImport(appDependencies(this))
}
}
// Helper for accessing the App from any context.
fun Context.asApp() = this.applicationContext as App
Inside my AppTestRunner class, I declare the configuration to be mutable. That way I can reset it's configuration between each and every test.
class AppTestRunner : AndroidJUnitRunner() {
override fun callApplicationOnCreate(app: Application) {
app.asApp().kodein.mutable = true
super.callApplicationOnCreate(app)
}
}
I have created a JUnit rule that reset the dependency graph before every test.
class ResetKodeinRule : ExternalResource() {
override fun before() {
val app = InstrumentationRegistry.getInstrumentation().targetContext.asApp()
app.kodein.clear()
app.kodein.addImport(appDependencies(app))
}
}
In my tests I can now retrieve the App.kodein instance and inject mocks that override dependencies of the original graph. The only thing that needs to be guaranteed is that the tested activity is launched after configuring mocks, or behavior is not predictable.

Categories

Resources