How to start koin from another class instead of Application class - android

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

Related

Integrate Koin in AAR

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.

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

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

Return internal class calling object function

I'm testing with Kotlin and I'm writing a small library to be imported and used by a test App project.
In the library project I marked my classes as internal because I don't want them to be visible for the App project, but I would like to have a single entry point for the library, and for that I am using a Kotlin object like shown below
LIBRARY
object Library {
fun getComponent() = AwesomeComponent()
}
internal class AwesomeComponent() {
// some implementation
}
TEST APP
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val component = Library.getComponent()
}
}
The problem is that this doesn't compile because the function in the Library object returns an internal type and therefore need to be marked as internal as well, but doing so would hide the function from the TestApp.
Another option would be to not have the internal modifier at all so the TestApp can see the Library method, but then it can also see the classes inside the Library project
Is there an easy solution that I am overlooking here or does it need to go through re-planning of packages and structure of the Library project? (not sure how to do it in that case)
You have to publish some sort of public API for the app module to be able to use the component that the getComponent() method returns. If you want to publish minimal information about your library, you can have it return an interface that contains only the publicly available method calls to the library, and make your class implement that interface:
object Library {
fun getComponent(): IAwesomeComponent = AwesomeComponent()
}
interface IAwesomeComponent {
// methods you want to call on the component in the app module
}
internal class AwesomeComponent(): IAwesomeComponent {
// implementations of the interface methods
}

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