diff --git a/docs/quickstart/android-annotations.md b/docs/quickstart/android-annotations.md new file mode 100755 index 000000000..d4b07c2a8 --- /dev/null +++ b/docs/quickstart/android-annotations.md @@ -0,0 +1,269 @@ +--- +title: Android - Annotations +--- + +> This tutorial lets you write an Android application and use Koin dependency injection to retrieve your components. +> You need around __10/15 min__ to do the tutorial. + +## Get the code + +:::info +[The source code is available at on Github](https://github.com/InsertKoinIO/koin-getting-started/tree/main/android) +::: + +## Gradle Setup + +Let's configure the KSP Plugin like this: + +```groovy +apply plugin: 'com.google.devtools.ksp' + +android { + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + test.java.srcDirs += 'src/test/kotlin' + } + // For KSP + applicationVariants.configureEach { variant -> + kotlin.sourceSets { + getByName(name) { + kotlin.srcDir("build/generated/ksp/${variant.name}/kotlin") + } + } + } +} +``` + +Add the Koin Android dependency like below: + +```groovy +dependencies { + // Koin + implementation "io.insert-koin:koin-android:$koin_version" + implementation "io.insert-koin:koin-annotations:$koin_ksp_version" + ksp "io.insert-koin:koin-ksp-compiler:$koin_ksp_version" +} +``` + + + +## Application Overview + +The idea of the application is to manage a list of users, and display it in our `MainActivity` class with a Presenter or a ViewModel: + +> Users -> UserRepository -> (Presenter or ViewModel) -> MainActivity + +## The "User" Data + +We will manage a collection of Users. Here is the data class: + +```kotlin +data class User(val name : String) +``` + +We create a "Repository" component to manage the list of users (add users or find one by name). Here below, the `UserRepository` interface and its implementation: + +```kotlin +interface UserRepository { + fun findUser(name : String): User? + fun addUsers(users : List) +} + +class UserRepositoryImpl : UserRepository { + + private val _users = arrayListOf() + + override fun findUser(name: String): User? { + return _users.firstOrNull { it.name == name } + } + + override fun addUsers(users : List) { + _users.addAll(users) + } +} +``` + +## The Koin module + +Let's declare a `AppModule` module class like below. + +```kotlin +@Module +@ComponentScan("org.koin.sample") +class AppModule +``` + +* We use the `@Module` to declare our class as Koin module +* The `@ComponentScan("org.koin.sample")` allow to scann any Koin definition in `"org.koin.sample"`package + +Let's simply add `@Single` on `UserRepositoryImpl` class to declare it as singleton: + +```kotlin +@Single +class UserRepositoryImpl : UserRepository { + // ... +} +``` + +## Displaying User with Presenter + +Let's write a presenter component to display a user: + +```kotlin +class UserPresenter(private val repository: UserRepository) { + + fun sayHello(name : String) : String{ + val foundUser = repository.findUser(name) + return foundUser?.let { "Hello '$it' from $this" } ?: "User '$name' not found!" + } +} +``` + +> UserRepository is referenced in UserPresenter`s constructor + +We declare `UserPresenter` in our Koin module. We declare it as a `factory` definition with the `@Factory` annotation, to not keep any instance in memory (avoid any leak with Android lifecycle): + +```kotlin +@Factory +class UserPresenter(private val repository: UserRepository) { + // ... +} +``` + +## Injecting Dependencies in Android + +The `UserPresenter` component will be created, resolving the `UserRepository` instance with it. To get it into our Activity, let's inject it with the `by inject()` delegate function: + +```kotlin +class MainActivity : AppCompatActivity() { + + private val presenter: UserPresenter by inject() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + //... + } +} +``` + +That's it, your app is ready. + +:::info +The `by inject()` function allows us to retrieve Koin instances, in Android components runtime (Activity, fragment, Service...) +::: + +## Start Koin + +We need to start Koin with our Android application. Just call the `startKoin()` function in the application's main entry point, our `MainApplication` class: + +```kotlin +// generated +import org.koin.ksp.generated.* + +class MainApplication : Application(){ + override fun onCreate() { + super.onCreate() + + startKoin{ + androidLogger() + androidContext(this@MainApplication) + modules(AppModule().module) + } + } +} +``` + +The Koin module is generated from `AppModule` with the `.module` extension: Just use the `AppModule().module` expression to get the Koin module from the annotations. + +:::info +The `import org.koin.ksp.generated.*` import is required to allow to use generated Koin module content +::: + +## Displaying User with ViewModel + +Let's write a ViewModel component to display a user: + +```kotlin +@KoinViewModel +class UserViewModel(private val repository: UserRepository) : ViewModel() { + + fun sayHello(name : String) : String{ + val foundUser = repository.findUser(name) + return foundUser?.let { "Hello '$it' from $this" } ?: "User '$name' not found!" + } +} +``` + +> UserRepository is referenced in UserViewModel`s constructor + +The `UserViewModel` is tagged with `@KoinViewModel` annotation to declare the Koin ViewModel definition, to not keep any instance in memory (avoid any leak with Android lifecycle). + + +## Injecting ViewModel in Android + +The `UserViewModel` component will be created, resolving the `UserRepository` instance with it. To get it into our Activity, let's inject it with the `by viewModel()` delegate function: + +```kotlin +class MainActivity : AppCompatActivity() { + + private val viewModel: UserViewModel by viewModel() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + //... + } +} +``` + +## Compile Time Checks + +Koin Annotations allows to check your Koin configuration at compile time. This is available by jusing the following Gradle option: + +```groovy +ksp { + arg("KOIN_CONFIG_CHECK","true") +} +``` + +## Verifying your App! + +We can ensure that our Koin configuration is good before launching our app, by verifying our Koin configuration with a simple JUnit Test. + +### Gradle Setup + +Add the Koin Android dependency like below: + +```groovy +// Add Maven Central to your repositories if needed +repositories { + mavenCentral() +} + +dependencies { + + // Koin for Tests + testImplementation "io.insert-koin:koin-test-junit4:$koin_version" +} +``` + +### Checking your modules + +The `verify()` function allow to verify the given Koin modules: + +```kotlin +class CheckModulesTest : KoinTest { + + @Test + fun checkAllModules() { + + AppModule().module.verify( + extraTypes = listOf( + SavedStateHandle::class + )) + } +} +``` + +With just a JUnit test, you can ensure your definitions configuration are not missing anything! diff --git a/docs/quickstart/android-compose.md b/docs/quickstart/android-compose.md new file mode 100755 index 000000000..e7ef99510 --- /dev/null +++ b/docs/quickstart/android-compose.md @@ -0,0 +1,245 @@ +--- +title: Android - Jetpack Compose +--- + +> This tutorial lets you write an Android application and use Koin dependency injection to retrieve your components. +> You need around __10 min__ to do the tutorial. + +## Get the code + +:::info +[The source code is available at on Github](https://github.com/InsertKoinIO/koin-getting-started/tree/main/android-compose) +::: + +## Gradle Setup + +Add the Koin Android dependency like below: + +```groovy +dependencies { + + // Koin for Android + implementation "io.insert-koin:koin-androidx-compose:$koin_version" +} +``` + +## Application Overview + +The idea of the application is to manage a list of users, and display it in our `MainActivity` class with a Presenter or a ViewModel: + +> Users -> UserRepository -> (Presenter or ViewModel) -> Composable + +## The "User" Data + +We will manage a collection of Users. Here is the data class: + +```kotlin +data class User(val name : String) +``` + +We create a "Repository" component to manage the list of users (add users or find one by name). Here below, the `UserRepository` interface and its implementation: + +```kotlin +interface UserRepository { + fun findUser(name : String): User? + fun addUsers(users : List) +} + +class UserRepositoryImpl : UserRepository { + + private val _users = arrayListOf() + + override fun findUser(name: String): User? { + return _users.firstOrNull { it.name == name } + } + + override fun addUsers(users : List) { + _users.addAll(users) + } +} +``` + +## The Koin module + +Use the `module` function to declare a Koin module. A Koin module is the place where we define all our components to be injected. + +```kotlin +val appModule = module { + +} +``` + +Let's declare our first component. We want a singleton of `UserRepository`, by creating an instance of `UserRepositoryImpl` + +```kotlin +val appModule = module { + single { UserRepositoryImpl() } +} +``` + +## Displaying User with UserViewModel + +### The `UserViewModel` class + +Let's write a ViewModel component to display a user: + +```kotlin +class UserViewModel(private val repository: UserRepository) : ViewModel() { + + fun sayHello(name : String) : String{ + val foundUser = repository.findUser(name) + return foundUser?.let { "Hello '$it' from $this" } ?: "User '$name' not found!" + } +} +``` + +> UserRepository is referenced in UserViewModel's constructor + +We declare `UserViewModel` in our Koin module. We declare it as a `viewModel` definition, to not keep any instance in memory (avoid any leak with Android lifecycle): + +```kotlin +val appModule = module { + single { UserRepositoryImpl() } + viewModel { MyViewModel(get()) } +} +``` + +> The `get()` function allow to ask Koin to resolve the needed dependency. + +### Injecting ViewModel in Compose + +The `UserViewModel` component will be created, resolving the `UserRepository` instance with it. To get it into our Activity, let's inject it with the `koinViewModel()` function: + +```kotlin +@Composable +fun ViewModelInject(userName : String, viewModel: UserViewModel = koinViewModel()){ + Text(text = viewModel.sayHello(userName), modifier = Modifier.padding(8.dp)) +} +``` + +:::info +The `koinViewModel` function allows us to retrieve a ViewModel instances, create the associated ViewModel Factory for you and bind it to the lifecycle +::: + +## Displaying User with UserStateHolder + +### The `UserStateHolder` class + +Let's write a ViewModel component to display a user: + +```kotlin +class UserStateHolder(private val repository: UserRepository) { + + fun sayHello(name : String) : String{ + val foundUser = repository.findUser(name) + return foundUser?.let { "Hello '$it' from $this" } ?: "User '$name' not found!" + } +} +``` + +> UserRepository is referenced in UserViewModel's constructor + +We declare `UserViewModel` in our Koin module. We declare it as a `viewModel` definition, to not keep any instance in memory (avoid any leak with Android lifecycle): + +```kotlin +val appModule = module { + single { UserRepositoryImpl() } + factory { UserStateHolder(get()) } +} +``` + +### Injecting UserStateHolder in Compose + +The `UserViewModel` component will be created, resolving the `UserRepository` instance with it. To get it into our Activity, let's inject it with the `get()` function: + +```kotlin +@Composable +fun FactoryInject(userName : String, presenter: UserStateHolder = get()){ + Text(text = presenter.sayHello(userName), modifier = Modifier.padding(8.dp)) +} +``` + +:::info +The `get` function allows us to retrieve a ViewModel instances, create the associated ViewModel Factory for you and bind it to the lifecycle +::: + + +## Start Koin + +We need to start Koin with our Android application. Just call the `startKoin()` function in the application's main entry point, our `MainApplication` class: + +```kotlin +class MainApplication : Application(){ + override fun onCreate() { + super.onCreate() + + startKoin{ + androidLogger() + androidContext(this@MainApplication) + modules(appModule) + } + } +} +``` + +:::info +The `modules()` function in `startKoin` load the given list of modules +::: + +## Koin module: classic or constructor DSL? + +Here is the Koin moduel declaration for our app: + +```kotlin +val appModule = module { + single { HelloRepositoryImpl() } + viewModel { MyViewModel(get()) } +} +``` + +We can write it in a more compact way, by using constructors: + +```kotlin +val appModule = module { + singleOf(::UserRepositoryImpl) { bind() } + viewModelOf(::UserViewModel) +} +``` + +## Verifying your App! + +We can ensure that our Koin configuration is good before launching our app, by verifying our Koin configuration with a simple JUnit Test. + +### Gradle Setup + +Add the Koin Android dependency like below: + +```groovy +// Add Maven Central to your repositories if needed +repositories { + mavenCentral() +} + +dependencies { + + // Koin for Tests + testImplementation "io.insert-koin:koin-test-junit4:$koin_version" +} +``` + +### Checking your modules + +The `verify()` function allow to verify the given Koin modules: + +```kotlin +class CheckModulesTest : KoinTest { + + @Test + fun checkAllModules() { + appModule.verify() + } +} +``` + +With just a JUnit test, you can ensure your definitions configuration are not missing anything! + diff --git a/docs/quickstart/android-viewmodel.md b/docs/quickstart/android-viewmodel.md new file mode 100755 index 000000000..5f6b5d52a --- /dev/null +++ b/docs/quickstart/android-viewmodel.md @@ -0,0 +1,207 @@ +--- +title: Android - ViewModel +--- + +> This tutorial lets you write an Android application and use Koin dependency injection to retrieve your components. +> You need around __10/15 min__ to do the tutorial. + +## Get the code + +:::info +[The source code is available at on Github](https://github.com/InsertKoinIO/koin-getting-started/tree/main/android) +::: + +## Gradle Setup + +Add the Koin Android dependency like below: + +```groovy +dependencies { + + // Koin for Android + implementation "io.insert-koin:koin-android:$koin_version" +} +``` + +## Application Overview + +The idea of the application is to manage a list of users, and display it in our `MainActivity` class with a Presenter or a ViewModel: + +> Users -> UserRepository -> (Presenter or ViewModel) -> MainActivity + +## The "User" Data + +We will manage a collection of Users. Here is the data class: + +```kotlin +data class User(val name : String) +``` + +We create a "Repository" component to manage the list of users (add users or find one by name). Here below, the `UserRepository` interface and its implementation: + +```kotlin +interface UserRepository { + fun findUser(name : String): User? + fun addUsers(users : List) +} + +class UserRepositoryImpl : UserRepository { + + private val _users = arrayListOf() + + override fun findUser(name: String): User? { + return _users.firstOrNull { it.name == name } + } + + override fun addUsers(users : List) { + _users.addAll(users) + } +} +``` + +## The Koin module + +Use the `module` function to declare a Koin module. A Koin module is the place where we define all our components to be injected. + +```kotlin +val appModule = module { + +} +``` + +Let's declare our first component. We want a singleton of `UserRepository`, by creating an instance of `UserRepositoryImpl` + +```kotlin +val appModule = module { + single { UserRepositoryImpl() } +} +``` + +## Displaying User with ViewModel + +Let's write a ViewModel component to display a user: + +```kotlin +class UserViewModel(private val repository: UserRepository) : ViewModel() { + + fun sayHello(name : String) : String{ + val foundUser = repository.findUser(name) + return foundUser?.let { "Hello '$it' from $this" } ?: "User '$name' not found!" + } +} +``` + +> UserRepository is referenced in UserViewModel`s constructor + +We declare `UserViewModel` in our Koin module. We declare it as a `viewModel` definition, to not keep any instance in memory (avoid any leak with Android lifecycle): + +```kotlin +val appModule = module { + single { UserRepositoryImpl() } + viewModel { MyViewModel(get()) } +} +``` + +> The `get()` function allow to ask Koin to resolve the needed dependency. + +## Injecting ViewModel in Android + +The `UserViewModel` component will be created, resolving the `UserRepository` instance with it. To get it into our Activity, let's inject it with the `by viewModel()` delegate function: + +```kotlin +class MainActivity : AppCompatActivity() { + + private val viewModel: UserViewModel by viewModel() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + //... + } +} +``` + +That's it, your app is ready. + +:::info +The `by viewModel()` function allows us to retrieve a ViewModel instances, create the associated ViewModel Factory for you and bind it to the lifecycle +::: + +## Start Koin + +We need to start Koin with our Android application. Just call the `startKoin()` function in the application's main entry point, our `MainApplication` class: + +```kotlin +class MainApplication : Application(){ + override fun onCreate() { + super.onCreate() + + startKoin{ + androidLogger() + androidContext(this@MainApplication) + modules(appModule) + } + } +} +``` + +:::info +The `modules()` function in `startKoin` load the given list of modules +::: + +## Koin module: classic or constructor DSL? + +Here is the Koin moduel declaration for our app: + +```kotlin +val appModule = module { + single { HelloRepositoryImpl() } + viewModel { MyViewModel(get()) } +} +``` + +We can write it in a more compact way, by using constructors: + +```kotlin +val appModule = module { + singleOf(::UserRepositoryImpl) { bind() } + viewModelOf(::UserViewModel) +} +``` + +## Verifying your App! + +We can ensure that our Koin configuration is good before launching our app, by verifying our Koin configuration with a simple JUnit Test. + +### Gradle Setup + +Add the Koin Android dependency like below: + +```groovy +// Add Maven Central to your repositories if needed +repositories { + mavenCentral() +} + +dependencies { + + // Koin for Tests + testImplementation "io.insert-koin:koin-test-junit4:$koin_version" +} +``` + +### Checking your modules + +The `verify()` function allow to verify the given Koin modules: + +```kotlin +class CheckModulesTest : KoinTest { + + @Test + fun checkAllModules() { + appModule.verify() + } +} +``` + +With just a JUnit test, you can ensure your definitions configuration are not missing anything! diff --git a/docs/quickstart/android.md b/docs/quickstart/android.md new file mode 100755 index 000000000..96d21e02f --- /dev/null +++ b/docs/quickstart/android.md @@ -0,0 +1,207 @@ +--- +title: Android +--- + +> This tutorial lets you write an Android application and use Koin dependency injection to retrieve your components. +> You need around __10/15 min__ to do the tutorial. + +## Get the code + +:::info +[The source code is available at on Github](https://github.com/InsertKoinIO/koin-getting-started/tree/main/android) +::: + +## Gradle Setup + +Add the Koin Android dependency like below: + +```groovy +dependencies { + + // Koin for Android + implementation "io.insert-koin:koin-android:$koin_version" +} +``` + +## Application Overview + +The idea of the application is to manage a list of users, and display it in our `MainActivity` class with a Presenter or a ViewModel: + +> Users -> UserRepository -> (Presenter or ViewModel) -> MainActivity + +## The "User" Data + +We will manage a collection of Users. Here is the data class: + +```kotlin +data class User(val name : String) +``` + +We create a "Repository" component to manage the list of users (add users or find one by name). Here below, the `UserRepository` interface and its implementation: + +```kotlin +interface UserRepository { + fun findUser(name : String): User? + fun addUsers(users : List) +} + +class UserRepositoryImpl : UserRepository { + + private val _users = arrayListOf() + + override fun findUser(name: String): User? { + return _users.firstOrNull { it.name == name } + } + + override fun addUsers(users : List) { + _users.addAll(users) + } +} +``` + +## The Koin module + +Use the `module` function to declare a Koin module. A Koin module is the place where we define all our components to be injected. + +```kotlin +val appModule = module { + +} +``` + +Let's declare our first component. We want a singleton of `UserRepository`, by creating an instance of `UserRepositoryImpl` + +```kotlin +val appModule = module { + single { UserRepositoryImpl() } +} +``` + +## Displaying User with Presenter + +Let's write a presenter component to display a user: + +```kotlin +class UserPresenter(private val repository: UserRepository) { + + fun sayHello(name : String) : String{ + val foundUser = repository.findUser(name) + return foundUser?.let { "Hello '$it' from $this" } ?: "User '$name' not found!" + } +} +``` + +> UserRepository is referenced in UserPresenter`s constructor + +We declare `UserPresenter` in our Koin module. We declare it as a `factory` definition, to not keep any instance in memory (avoid any leak with Android lifecycle): + +```kotlin +val appModule = module { + single { UserRepositoryImpl() } + factory { MyPresenter(get()) } +} +``` + +> The `get()` function allow to ask Koin to resolve the needed dependency. + +## Injecting Dependencies in Android + +The `UserPresenter` component will be created, resolving the `UserRepository` instance with it. To get it into our Activity, let's inject it with the `by inject()` delegate function: + +```kotlin +class MainActivity : AppCompatActivity() { + + private val presenter: UserPresenter by inject() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + //... + } +} +``` + +That's it, your app is ready. + +:::info +The `by inject()` function allows us to retrieve Koin instances, in Android components runtime (Activity, fragment, Service...) +::: + +## Start Koin + +We need to start Koin with our Android application. Just call the `startKoin()` function in the application's main entry point, our `MainApplication` class: + +```kotlin +class MainApplication : Application(){ + override fun onCreate() { + super.onCreate() + + startKoin{ + androidLogger() + androidContext(this@MainApplication) + modules(appModule) + } + } +} +``` + +:::info +The `modules()` function in `startKoin` load the given list of modules +::: + +## Koin module: classic or constructor DSL? + +Here is the Koin moduel declaration for our app: + +```kotlin +val appModule = module { + single { HelloRepositoryImpl() } + factory { MyPresenter(get()) } +} +``` + +We can write it in a more compact way, by using constructors: + +```kotlin +val appModule = module { + singleOf(::UserRepositoryImpl) { bind() } + factoryOf(::UserPresenter) +} +``` + +## Verifying your App! + +We can ensure that our Koin configuration is good before launching our app, by verifying our Koin configuration with a simple JUnit Test. + +### Gradle Setup + +Add the Koin Android dependency like below: + +```groovy +// Add Maven Central to your repositories if needed +repositories { + mavenCentral() +} + +dependencies { + + // Koin for Tests + testImplementation "io.insert-koin:koin-test-junit4:$koin_version" +} +``` + +### Checking your modules + +The `verify()` function allow to verify the given Koin modules: + +```kotlin +class CheckModulesTest : KoinTest { + + @Test + fun checkAllModules() { + appModule.verify() + } +} +``` + +With just a JUnit test, you can ensure your definitions configuration are not missing anything! diff --git a/docs/quickstart/junit-test.md b/docs/quickstart/junit-test.md new file mode 100644 index 000000000..a440648a1 --- /dev/null +++ b/docs/quickstart/junit-test.md @@ -0,0 +1,93 @@ +--- +title: JUnit Tests +--- + +> This tutorial lets you test a Kotlin application and use Koin inject and retrieve your components. + +## Get the code + +:::info +[The source code is available at on Github](https://github.com/InsertKoinIO/koin-getting-started/tree/main/kotlin) +::: + +## Gradle Setup + +First, add the Koin dependency like below: + +```groovy +dependencies { + // Koin testing tools + testCompile "io.insert-koin:koin-test:$koin_version" + // Needed JUnit version + testCompile "io.insert-koin:koin-test-junit4:$koin_version" +} +``` + +## Declared dependencies + +We reuse the `koin-core` getting-started project, to use the koin module: + +```kotlin +val helloModule = module { + single { HelloMessageData() } + single { HelloServiceImpl(get()) as HelloService } +} +``` + +## Writing our first Test + +To make our first test, let's write a simple Junit test file and extend it with `KoinTest`. We will be able then, to use `by inject()` operators. + +```kotlin +class HelloAppTest : KoinTest { + + val model by inject() + val service by inject() + + @get:Rule + val koinTestRule = KoinTestRule.create { + printLogger() + modules(helloModule) + } + + @Test + fun `unit test`() { + val helloApp = HelloApplication() + helloApp.sayHello() + + assertEquals(service, helloApp.helloService) + assertEquals("Hey, ${model.message}", service.hello()) + } +} +``` + +> We use the Koin KoinTestRule rule to start/stop our Koin context + +You can even make Mocks directly into MyPresenter, or test MyRepository. Those components doesn't have any link with Koin API. + +```kotlin +class HelloMockTest : KoinTest { + + @get:Rule + val koinTestRule = KoinTestRule.create { + printLogger(Level.DEBUG) + modules(helloModule) + } + + @get:Rule + val mockProvider = MockProviderRule.create { clazz -> + Mockito.mock(clazz.java) + } + + @Test + fun `mock test`() { + val service = declareMock { + given(hello()).willReturn("Hello Mock") + } + + HelloApplication().sayHello() + + Mockito.verify(service,times(1)).hello() + } +} +``` diff --git a/docs/quickstart/kmp.md b/docs/quickstart/kmp.md new file mode 100755 index 000000000..764edbebf --- /dev/null +++ b/docs/quickstart/kmp.md @@ -0,0 +1,204 @@ +--- +title: Kotlin Multiplatform - Mobile Apps +--- + +> This tutorial lets you write an Android application and use Koin dependency injection to retrieve your components. +> You need around __10/15 min__ to do the tutorial. + +## Get the code + +:::info +[The source code is available at on Github](https://github.com/InsertKoinIO/koin-getting-started/tree/main/kmp) +::: + +## Application Overview + +The idea of the application is to manage a list of users, and display it in our native UI, witha shared Presenter: + +`Users -> UserRepository -> Shared Presenter -> Native UI` + +## The "User" Data + +> All the common/shared code is located in `shared` Gradle project + +We will manage a collection of Users. Here is the data class: + +```kotlin +data class User(val name : String) +``` + +We create a "Repository" component to manage the list of users (add users or find one by name). Here below, the `UserRepository` interface and its implementation: + +```kotlin +interface UserRepository { + fun findUser(name : String): User? + fun addUsers(users : List) +} + +class UserRepositoryImpl : UserRepository { + + private val _users = arrayListOf() + + override fun findUser(name: String): User? { + return _users.firstOrNull { it.name == name } + } + + override fun addUsers(users : List) { + _users.addAll(users) + } +} +``` + +## The Shared Koin module + +Use the `module` function to declare a Koin module. A Koin module is the place where we define all our components to be injected. + +Let's declare our first component. We want a singleton of `UserRepository`, by creating an instance of `UserRepositoryImpl` + +```kotlin +module { + single { UserRepositoryImpl() } +} +``` + +## The Shared Presenter + +Let's write a presenter component to display a user: + +```kotlin +class KMPUserPresenter(private val repository: UserRepository) { + + fun sayHello() : String { + val name = DefaultData.DEFAULT_USER.name + val foundUser = repository.findUser(name) + return foundUser?.let { "Hello '$it' from $this" } ?: "User '$name' not found!" + } +} +``` + +> UserRepository is referenced in UserPresenter`s constructor + +We declare `UserPresenter` in our Koin module. We declare it as a `factory` definition, to not keep any instance in memory and let the native system hold it: + +```kotlin +fun appModule() = module { + single { UserRepositoryImpl() } + factory { KMPUserPresenter(get()) } +} +``` + +:::note +The Koin module is available as function to run (`appModule()` here), to be easily runned from iOS side, with `initKoin()` function. +::: + +## Injecting Dependencies in Android + +> All the Android app is located in `androidApp` Gradle project + +The `KMPUserPresenter` component will be created, resolving the `UserRepository` instance with it. To get it into our Activity, let's inject it with the `by inject()` delegate function: + +```kotlin +class MainActivity : AppCompatActivity() { + + private val presenter: UserPresenter by inject() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + //... + } +} +``` + +That's it, your app is ready. + +:::info +The `by inject()` function allows us to retrieve Koin instances, in Android components runtime (Activity, fragment, Service...) +::: + +We need to start Koin with our Android application. Just call the `startKoin()` function in the application's main entry point, our `MainApplication` class: + +```kotlin +class MainApplication : Application() { + + private val userRepository : UserRepository by inject() + + override fun onCreate() { + super.onCreate() + + startKoin { + androidContext(this@MainApplication) + androidLogger() + modules(appModule() + androidModule) + } + + userRepository.addUsers(DefaultData.DEFAULT_USERS) + } +} +``` + +:::info +The `modules()` function in `startKoin` load the given list of modules +::: + + +## Injecting Dependencies in iOS + +> All the iOS app is located in `iosApp` folder + +The `KMPUserPresenter` component will be created, resolving the `UserRepository` instance with it. To get it into our `ContentView`, we need to create a Helper class to boostrap Koin dependencies: + +```kotlin +class KMPUserPresenterHelper : KoinComponent { + + private val userPresenter : KMPUserPresenter by inject() + + fun sayHello(): String = userPresenter.sayHello() +} +``` + +That's it, you can just call `sayHello()` function from iOS part. + +```swift +import shared + +struct ContentView: View { + let helloText = KMPUserPresenterHelper().sayHello() + + var body: some View { + Text(helloText) + } +} +``` + +We need to start Koin with our iOS application. In the Kotlin shared code, we have a function to let us configure Koin (and setup default data): + +```kotlin +// in HelperKt.kt + +fun initKoin() { + // start Koin + val koinApp = startKoin { + modules(appModule()) + }.koin + + // load default users + koinApp.get().addUsers(DefaultData.DEFAULT_USERS) +} +``` + +Finally in the iOS main entry, we can call the `HelperKt.doInitKoin()` function that is calling our helper function above. + +```swift +@main +struct iOSApp: App { + + init() { + HelperKt.doInitKoin() + } + + //... +} +``` + + diff --git a/docs/quickstart/kotlin-annotations.md b/docs/quickstart/kotlin-annotations.md new file mode 100755 index 000000000..1d48c4708 --- /dev/null +++ b/docs/quickstart/kotlin-annotations.md @@ -0,0 +1,182 @@ +--- +title: Kotlin - Annotations +--- + +> This tutorial lets you write a Kotlin application and use Koin dependency injection to retrieve your components. +> You need around __10 min__ to do the tutorial. + +## Get the code + +:::info +[The source code is available at on Github](https://github.com/InsertKoinIO/koin-getting-started/tree/main/kotlin-annotations) +::: + +## Setup + +Let's configure the KSP plugin like below in Gradle: + +```groovy +plugins { + // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin. + id 'org.jetbrains.kotlin.jvm' version "$kotlin_version" + // Apply the application plugin to add support for building a CLI application in Java. + id "com.google.devtools.ksp" version "$ksp_version" + id 'application' +} + +// KSP - To use generated sources +sourceSets.main { + java.srcDirs("build/generated/ksp/main/kotlin") +} +``` + +Let's setup the dependencies like below: + +```groovy +dependencies { + + // Koin + implementation "io.insert-koin:koin-core:$koin_version" + implementation "io.insert-koin:koin-annotations:$koin_ksp_version" + ksp "io.insert-koin:koin-ksp-compiler:$koin_ksp_version" +} +``` + +## Application Overview + +The idea of the application is to manage a list of users, and display it in our `UserApplication` class: + +> Users -> UserRepository -> UserService -> UserApplication + +## The "User" Data + +We will manage a collection of Users. Here is the data class: + +```kotlin +data class User(val name : String) +``` + +We create a "Repository" component to manage the list of users (add users or find one by name). Here below, the `UserRepository` interface and its implementation: + +```kotlin +interface UserRepository { + fun findUser(name : String): User? + fun addUsers(users : List) +} + +class UserRepositoryImpl : UserRepository { + + private val _users = arrayListOf() + + override fun findUser(name: String): User? { + return _users.firstOrNull { it.name == name } + } + + override fun addUsers(users : List) { + _users.addAll(users) + } +} +``` + +## The Koin module + +Let's declare a `AppModule` module class like below. + +```kotlin +@Module +@ComponentScan("org.koin.sample") +class AppModule +``` + +* We use the `@Module` to declare our class as Koin module +* The `@ComponentScan("org.koin.sample")` allow to scann any Koin definition in `"org.koin.sample"`package + +Let's simply add `@Single` on `UserRepositoryImpl` class to declare it as singleton: + +```kotlin +@Single +class UserRepositoryImpl : UserRepository { + // ... +} +``` + +## The UserService Component + +Let's write the UserService component to request the default user: + +```kotlin +class UserService(private val userRepository: UserRepository) { + + fun getDefaultUser() : User = userRepository.findUser(DefaultData.DEFAULT_USER.name) ?: error("Can't find default user") +} +``` + +> UserRepository is referenced in UserPresenter`s constructor + +Let's simply add `@Single` on `UserService` class to declare it as singleton: + +```kotlin +@Single +class UserService(private val userRepository: UserRepository) { + // ... +} +``` + +## Injecting Dependencies in UserApplication + +The `UserApplication` class will help bootstrap instances out of Koin. It will resolve the `UserService`, thanks to `KoinComponent` interface. This allows to inject it with the `by inject()` delegate function: + +```kotlin +class UserApplication : KoinComponent { + + private val userService : UserService by inject() + + // display our data + fun sayHello(){ + val user = userService.getDefaultUser() + val message = "Hello '$user'!" + println(message) + } +} +``` + +That's it, your app is ready. + +:::info +The `by inject()` function allows us to retrieve Koin instances, in any class that extends `KoinComponent` +::: + + +## Start Koin + +We need to start Koin with our application. Just call the `startKoin()` function in the application's main entry point, our `main` function: + +```kotlin +// generated +import org.koin.ksp.generated.* + +fun main() { + startKoin { + modules(AppModule().module) + } + + UserApplication().sayHello() +} +``` + +The Koin module is generated from `AppModule` with the `.module` extension: Just use the `AppModule().module` expression to get the Koin module from the annotations. + + +:::info +The `import org.koin.ksp.generated.*` import is required to allow to use generated Koin module content +::: + +## Compile Time Checks + +Koin Annotations allows to check your Koin configuration at compile time. This is available by jusing the following Gradle option: + +```groovy +ksp { + arg("KOIN_CONFIG_CHECK","true") +} +``` \ No newline at end of file diff --git a/docs/quickstart/kotlin.md b/docs/quickstart/kotlin.md new file mode 100755 index 000000000..c9c70f087 --- /dev/null +++ b/docs/quickstart/kotlin.md @@ -0,0 +1,165 @@ +--- +title: Kotlin +--- + +> This tutorial lets you write a Kotlin application and use Koin dependency injection to retrieve your components. +> You need around __10 min__ to do the tutorial. + +## Get the code + +:::info +[The source code is available at on Github](https://github.com/InsertKoinIO/koin-getting-started/tree/main/kotlin) +::: + +## Setup + +First, check that the `koin-core` dependency is added like below: + +```groovy +dependencies { + + // Koin for Kotlin apps + compile "io.insert-koin:koin-core:$koin_version" +} +``` + +## Application Overview + +The idea of the application is to manage a list of users, and display it in our `UserApplication` class: + +> Users -> UserRepository -> UserService -> UserApplication + +## The "User" Data + +We will manage a collection of Users. Here is the data class: + +```kotlin +data class User(val name : String) +``` + +We create a "Repository" component to manage the list of users (add users or find one by name). Here below, the `UserRepository` interface and its implementation: + +```kotlin +interface UserRepository { + fun findUser(name : String): User? + fun addUsers(users : List) +} + +class UserRepositoryImpl : UserRepository { + + private val _users = arrayListOf() + + override fun findUser(name: String): User? { + return _users.firstOrNull { it.name == name } + } + + override fun addUsers(users : List) { + _users.addAll(users) + } +} +``` + +## The Koin module + +Use the `module` function to declare a Koin module. A Koin module is the place where we define all our components to be injected. + +```kotlin +val appModule = module { + +} +``` + +Let's declare our first component. We want a singleton of `UserRepository`, by creating an instance of `UserRepositoryImpl` + +```kotlin +val appModule = module { + single { UserRepositoryImpl() } +} +``` + +## The UserService Component + +Let's write the UserService component to request the default user: + +```kotlin +class UserService(private val userRepository: UserRepository) { + + fun getDefaultUser() : User = userRepository.findUser(DefaultData.DEFAULT_USER.name) ?: error("Can't find default user") +} +``` + +> UserRepository is referenced in UserPresenter`s constructor + +We declare `UserService` in our Koin module. We declare it as a `single` definition: + +```kotlin +val appModule = module { + single { UserRepositoryImpl() } + single { UserService(get()) } +} +``` + +> The `get()` function allow to ask Koin to resolve the needed dependency. + +## Injecting Dependencies in UserApplication + +The `UserApplication` class will help bootstrap instances out of Koin. It will resolve the `UserService`, thanks to `KoinComponent` interface. This allows to inject it with the `by inject()` delegate function: + +```kotlin +class UserApplication : KoinComponent { + + private val userService : UserService by inject() + + // display our data + fun sayHello(){ + val user = userService.getDefaultUser() + val message = "Hello '$user'!" + println(message) + } +} +``` + +That's it, your app is ready. + +:::info +The `by inject()` function allows us to retrieve Koin instances, in any class that extends `KoinComponent` +::: + + +## Start Koin + +We need to start Koin with our application. Just call the `startKoin()` function in the application's main entry point, our `main` function: + +```kotlin +fun main() { + startKoin { + modules(appModule) + } + + UserApplication().sayHello() +} +``` + +:::info +The `modules()` function in `startKoin` load the given list of modules +::: + +## Koin module: classic or constructor DSL? + +Here is the Koin moduel declaration for our app: + +```kotlin +val appModule = module { + single { UserRepositoryImpl() } + single { UserService(get()) } +} +``` + +We can write it in a more compact way, by using constructors: + +```kotlin +val appModule = module { + singleOf(::UserRepositoryImpl) { bind() } + singleOf(::UserService) +} +``` diff --git a/docs/quickstart/ktor.md b/docs/quickstart/ktor.md new file mode 100755 index 000000000..5806c43d7 --- /dev/null +++ b/docs/quickstart/ktor.md @@ -0,0 +1,184 @@ +--- +title: Ktor +--- + +> Ktor is a framework for building asynchronous servers and clients in connected systems using the powerful Kotlin programming language. We will use Ktor here, to build a simple web application. + +Let's go 🚀 + +## Get the code + +:::info +[The source code is available at on Github](https://github.com/InsertKoinIO/koin-getting-started/tree/main/ktor) +::: + +## Gradle Setup + +First, add the Koin dependency like below: + +```kotlin +dependencies { + // Koin for Kotlin apps + implementation "io.insert-koin:koin-ktor:$koin_version" + implementation "io.insert-koin:koin-logger-slf4j:$koin_version" +} +``` + +## Application Overview + +The idea of the application is to manage a list of users, and display it in our `UserApplication` class: + +> Users -> UserRepository -> UserService -> UserApplication + +## The "User" Data + +We will manage a collection of Users. Here is the data class: + +```kotlin +data class User(val name : String) +``` + +We create a "Repository" component to manage the list of users (add users or find one by name). Here below, the `UserRepository` interface and its implementation: + +```kotlin +interface UserRepository { + fun findUser(name : String): User? + fun addUsers(users : List) +} + +class UserRepositoryImpl : UserRepository { + + private val _users = arrayListOf() + + override fun findUser(name: String): User? { + return _users.firstOrNull { it.name == name } + } + + override fun addUsers(users : List) { + _users.addAll(users) + } +} +``` + +## The Koin module + +Use the `module` function to declare a Koin module. A Koin module is the place where we define all our components to be injected. + +```kotlin +val appModule = module { + +} +``` + +Let's declare our first component. We want a singleton of `UserRepository`, by creating an instance of `UserRepositoryImpl` + +```kotlin +val appModule = module { + single { UserRepositoryImpl() } +} +``` + +## The UserService Component + +Let's write the UserService component to request the default user: + +```kotlin +class UserService(private val userRepository: UserRepository) { + + fun getDefaultUser() : User = userRepository.findUser(DefaultData.DEFAULT_USER.name) ?: error("Can't find default user") +} +``` + +> UserRepository is referenced in UserPresenter`s constructor + +We declare `UserService` in our Koin module. We declare it as a `single` definition: + +```kotlin +val appModule = module { + single { UserRepositoryImpl() } + single { UserService(get()) } +} +``` + +## HTTP Controller + +Finally, we need an HTTP Controller to create the HTTP Route. In Ktor is will be expressed through an Ktor extension function: + +```kotlin +fun Application.main() { + + // Lazy inject HelloService + val service by inject() + + // Routing section + routing { + get("/hello") { + call.respondText(service.sayHello()) + } + } +} +``` + +Check that your `application.conf` is configured like below, to help start the `Application.main` function: + +```kotlin +ktor { + deployment { + port = 8080 + + // For dev purpose + //autoreload = true + //watch = [org.koin.sample] + } + + application { + modules = [ org.koin.sample.UserApplicationKt.main ] + } +} +``` + +## Declare your dependencies + +Let's assemble our components with a Koin module: + +```kotlin +val appModule = module { + singleOf(::UserRepositoryImpl) { bind() } + singleOf(::UserService) +} +``` + +## Start and Inject + +Finally, let's start Koin from Ktor: + +```kotlin +fun Application.main() { + install(Koin) { + slf4jLogger() + modules(appModule) + } + + // Lazy inject HelloService + val service by inject() + service.saveDefaultUsers() + + // Routing section + routing { + get("/hello") { + call.respondText(service.sayHello()) + } + } +} +``` + +Let's start Ktor: + +```kotlin +fun main(args: Array) { + // Start Ktor + embeddedServer(Netty, commandLineEnvironment(args)).start(wait = true) +} +``` + +That's it! You're ready to go. Check the `http://localhost:8080/hello` url! diff --git a/docs/reference/koin-android/dsl-update.md b/docs/reference/koin-android/dsl-update.md index 1b7842677..76be6a817 100644 --- a/docs/reference/koin-android/dsl-update.md +++ b/docs/reference/koin-android/dsl-update.md @@ -4,9 +4,9 @@ title: Constructor DSL for Android ## New Constructor DSL (Since 3.2) -Koin now offer a new kind of DSL keyword that allow you to target a class constructor directly, and avoid to to have type your definition within a lambda expression. +Koin now offer a new kind of DSL keyword that allow you to target a class constructor directly, and avoid to have type your definition within a lambda expression. -Check the new [Constructor DSL](../koin-core/dsl-update.md#constructor-dsl-since-32) section for more details. +Check the new [Constructor DSL](/docs/reference/koin-core/dsl-update.md#constructor-dsl-since-32) section for more details. For Android, this implies the following new constructor DSL Keyword: diff --git a/docs/reference/koin-android/fragment-factory.md b/docs/reference/koin-android/fragment-factory.md index 94453a34b..8a9f2bc34 100644 --- a/docs/reference/koin-android/fragment-factory.md +++ b/docs/reference/koin-android/fragment-factory.md @@ -8,7 +8,7 @@ https://developer.android.com/jetpack/androidx/releases/fragment ## Fragment Factory -Since `2.1.0-alpha-3` version, has been introduce the `FragmentFactory`, a class dedicated to create instance of `Fragment` class: +Since `2.1.0-alpha-3` version, has been introduced the `FragmentFactory`, a class dedicated to create instance of `Fragment` class: https://developer.android.com/reference/kotlin/androidx/fragment/app/FragmentFactory @@ -16,7 +16,7 @@ Koin can bring a `KoinFragmentFactory` to help you inject your `Fragment` instan ## Setup Fragment Factory -At start, in your KoinApplication declaration, use the `fragmentFactory()` keyword to setup a default `KoinFragmentFactory` instance: +At start, in your KoinApplication declaration, use the `fragmentFactory()` keyword to setting up a default `KoinFragmentFactory` instance: ```kotlin startKoin { @@ -49,7 +49,7 @@ val appModule = module { ## Get your Fragment -From your host `Activity` class, setup your fragment factory with `setupKoinFragmentFactory()`: +From your host `Activity` class, setting up your fragment factory with `setupKoinFragmentFactory()`: ```kotlin class MyActivity : AppCompatActivity() { @@ -95,7 +95,7 @@ val appModule = module { } ``` -and setup your Koin Fragment Factory with your scope: `setupKoinFragmentFactory(lifecycleScope)` +and setting up your Koin Fragment Factory with your scope: `setupKoinFragmentFactory(lifecycleScope)` ```kotlin class MyActivity : AppCompatActivity() { diff --git a/docs/reference/koin-android/get-instances.md b/docs/reference/koin-android/get-instances.md index 4655f6394..dc132a7c2 100644 --- a/docs/reference/koin-android/get-instances.md +++ b/docs/reference/koin-android/get-instances.md @@ -3,7 +3,7 @@ title: Injecting in Android --- -Once you have declared some modules and you have started Koin, how can you retrieve your instances in your +Once you have declared some modules, and you have started Koin, how can you retrieve your instances in your Android Activity Fragments or Services? ## Ready for Android Classes @@ -48,12 +48,12 @@ override fun onCreate(savedInstanceState: Bundle?) { ``` :::info -if you class doesn't have extensions, just add KoinComponent interface If you need to `inject()` or `get()` an instance from another class. +if your class doesn't have extensions, just implement the `KoinComponent` interface in it to `inject()` or `get()` an instance from another class. ::: ## Using the Android Context in a Definition -Once your `Application` class you can use `androidContext` function: +Once your `Application` class configures Koin you can use the `androidContext` function to inject Android Context so that it can be resolved later when you need it in modules: ```kotlin class MainApplication : Application() { @@ -62,7 +62,7 @@ class MainApplication : Application() { super.onCreate() startKoin { - //inject Android context + // inject Android context androidContext(this@MainApplication) // ... } diff --git a/docs/reference/koin-android/instrumented-testing.md b/docs/reference/koin-android/instrumented-testing.md index 32454e1f0..f25ff911c 100644 --- a/docs/reference/koin-android/instrumented-testing.md +++ b/docs/reference/koin-android/instrumented-testing.md @@ -4,7 +4,7 @@ title: Android Instrumented Testing ## Override production modules in a custom Application class -Unlike [unit tests](../koin-test/testing.md), where you effectively call start Koin in each test class (i.e. `startKoin` or `KoinTestExtension`), in Instrumented tests Koin is started by your `Application` class. +Unlike [unit tests](/docs/reference/koin-test/testing.md), where you effectively call start Koin in each test class (i.e. `startKoin` or `KoinTestExtension`), in Instrumented tests Koin is started by your `Application` class. For overriding production Koin modules, `loadModules` and `unloadModules` are often unsafe because the changes are not applied immediately. Instead, the recommended approach is to add a `module` of your overrides to `modules` used by `startKoin` in the `Application` class. If you want to keep the class that extends `Application` of your application untouched, you can create another one inside the `AndroidTest` package like: diff --git a/docs/reference/koin-android/modules-android.md b/docs/reference/koin-android/modules-android.md index 8c2773dba..992ddccb4 100644 --- a/docs/reference/koin-android/modules-android.md +++ b/docs/reference/koin-android/modules-android.md @@ -6,8 +6,8 @@ By using Koin, you describe definitions in modules. In this section we will see ## Using several modules -Components doesn't have to be necessarily in the same module. A module is a logical space to help you organize your definitions, and can depend on definitions from other -module. Definitions are lazy, and then are resolved only when a a component is requesting it. +Components don't have to be necessarily in the same module. A module is a logical space to help you organize your definitions, and can depend on definitions from another +module. Definitions are lazy, and they are resolved only when a component requests them. Let's take an example, with linked components in separate modules: @@ -47,7 +47,7 @@ class MainApplication : Application() { ``` Up to you to organise your self per Gradle module, and gather several Koin modules. -> Check [Koin Modules Section](../koin-core/modules) for more details +> Check [Koin Modules Section](/docs/reference/koin-core/modules) for more details ## Module Includes (since 3.2) @@ -124,7 +124,7 @@ Notice that all modules will be included only once: `dataModule`, `domainModule` ## Reducing Startup time with background module loading -You can now declared "lazy" Koin module, to avoid trigger any pre allocation of resources and load them in background with Koin start. This can help avoid to block Android starting process, by passing lazy modules to be loaded in background. +You can now declare "lazy" Koin module, to avoid trigger any pre allocation of resources and load them in background with Koin start. This can help avoid to block Android starting process, by passing lazy modules to be loaded in background. - `lazyModule` - declare a Lazy Kotlin version of Koin Module - `Module.includes` - allow to include lazy Modules diff --git a/docs/reference/koin-android/scope.md b/docs/reference/koin-android/scope.md index 4d0e81ddb..52b21a024 100644 --- a/docs/reference/koin-android/scope.md +++ b/docs/reference/koin-android/scope.md @@ -60,7 +60,7 @@ val androidModule = module { on it and can't totally drop it via garbage collection. ::: -## Scope for Android Components (3.2.1 update) +## Scope for Android Components (since 3.2.1) ### Declare an Android Scope @@ -109,7 +109,7 @@ abstract class ScopeActivity( } ``` -We need to use the `AndroidScopeComponent` interface and implement the `scope` property. This will setup the default scope used by your class. +We need to use the `AndroidScopeComponent` interface and implement the `scope` property. This will setting up the default scope used by your class. ### Android Scope API @@ -132,7 +132,7 @@ class MyActivity() : AppCompatActivity(contentLayoutId), AndroidScopeComponent { } ``` -We can also setup a retained scope (backed by a ViewModel lifecycle) with the following: +We can also to setting up a retained scope (backed by a ViewModel lifecycle) with the following: ```kotlin class MyActivity() : AppCompatActivity(contentLayoutId), AndroidScopeComponent { @@ -164,6 +164,75 @@ class MyActivity() : AppCompatActivity(contentLayoutId), AndroidScopeComponent { If you try to access Scope from `onDestroy()` function, scope will be already closed. ::: +### ViewModel Scope (since 3.5.4) + +ViewModel is only created against root scope to avoid any leaking (leaking Activity or Fragment ...). This guard for the visibility problem, where ViewModel could have access to incompatible scopes. + +:::warn +ViewModel can't access to Activity or Fragment scope. Why? Because ViewModel is lasting long than Activity and Fragment, and then it would leak dependencies outside of proper scopes. +::: + +:::note +If you _really_ need to bridge a dependency from outside a ViewModel scope, you can use "injected parameters" to pass some objects to your ViewModel: `viewModel { p -> }` +::: + +`ScopeViewModel` is a new class to help work on ViewModel scope. This handle ViewModel's scope creation, and provide `scope` property to allow inject with `by scope.inject()`: + +```kotlin +module { + viewModelOf(::MyScopeViewModel) + scope { + scopedOf(::Session) + } +} + +class MyScopeViewModel : ScopeViewModel() { + + // on onCleared, scope is closed + + // injected from current MyScopeViewModel's scope + val session by scope.inject() + +} +``` + +By using `ScopeViewModel` you can also overrode `onCloseScope()` function, to run code before scope is being closed. + +:::note +All instances inside a ViewModel scope have the same visibility and will survive for lifetime of ViewModel instance, until ViewModel's onCleared function is called +::: + +For example, Once an Activity or fragment has created a ViewModel, the associated scope is created: + +```kotlin +class MyActivity : AppCompatActivity() { + + // Create ViewModel and its scope + val myViewModel by viewModel() + +} +``` + +Once your ViewModel is created, all associated dependencies from within this scope can be created and injected. + +To implement manually your ViewModel scope without `ScopeViewModel` class proceed as follow: + +```kotlin +class MyScopeViewModel : ViewModel(), KoinScopeComponent { + + override val scope: Scope = createScope(this) + + // inject your dependency + val session by scope.inject() + + // clear scope + override fun onCleared() { + super.onCleared() + scope.close() + } +} +``` + ## Scope Links Scope links allow to share instances between components with custom scopes. diff --git a/docs/reference/koin-android/start.md b/docs/reference/koin-android/start.md index b86a8cdf3..f495b08f6 100644 --- a/docs/reference/koin-android/start.md +++ b/docs/reference/koin-android/start.md @@ -2,11 +2,11 @@ title: Start Koin on Android --- -The `koin-android` project is dedicated to provide Koin powers to Android world. See the [Android setup](../../setup/v3.2#android) section for more details. +The `koin-android` project is dedicated to provide Koin powers to Android world. See the [Android setup](/docs/setup/koin#android) section for more details. ## From your Application class -From your `Application` class you can use the `startKoin` function and inject the Android context with `androidContext` as follow: +From your `Application` class you can use the `startKoin` function and inject the Android context with `androidContext` as follows: ```kotlin class MainApplication : Application() { @@ -22,13 +22,12 @@ class MainApplication : Application() { // Load modules modules(myAppModules) } - } } ``` :::info -You can also start Koin from anywhere if you don't ant to start it from your Application class. +You can also start Koin from anywhere if you don't want to start it from your Application class. ::: If you need to start Koin from another Android class, you can use the `startKoin` function and provide your Android `Context` @@ -48,10 +47,10 @@ From your Koin configuration (in `startKoin { }` block code), you can also confi ### Koin Logging for Android -Within your `KoinApplication` instance, we have an extension `androidLogger` which use the `AndroidLogger()`# +Within your `KoinApplication` instance, we have an extension `androidLogger` which uses the `AndroidLogger()` class. This logger is an Android implementation of the Koin logger. -Up to you to change this logger if it doesn't suits to your needs. +Up to you to change this logger if it doesn't suit to your needs. ```kotlin startKoin { @@ -69,8 +68,7 @@ You can use Koin properties in the `assets/koin.properties` file, to store keys/ startKoin { // ... // use properties from assets/koin.properties - androidFileProperties() - + androidFileProperties() } ``` diff --git a/docs/reference/koin-android/viewmodel.md b/docs/reference/koin-android/viewmodel.md index 72a246ce9..a2e4cbbdf 100644 --- a/docs/reference/koin-android/viewmodel.md +++ b/docs/reference/koin-android/viewmodel.md @@ -21,7 +21,7 @@ Your declared component must at least extends the `android.arch.lifecycle.ViewMo and use the `get()` function to inject dependencies. :::info -The `viewModel`/`viewModelOf` keyword helps declaring a factory instance of ViewModel. This instance will be handled by internal ViewModelFactory and reattach ViewModel instance if needed. +The `viewModel`/`viewModelOf` keyword helps to declare a factory instance of ViewModel. This instance will be handled by internal ViewModelFactory and reattach ViewModel instance if needed. It also will let inject parameters. ::: @@ -41,6 +41,10 @@ class DetailActivity : AppCompatActivity() { } ``` +:::note +ViewModel key is calculated against Key and/or Qualifier +::: + ## Activity Shared ViewModel One ViewModel instance can be shared between Fragments and their host Activity. @@ -165,7 +169,7 @@ All `stateViewModel` functions are deprecated. You can just use the regular `vie ## Navigation Graph ViewModel -You can scope a ViewModel instance to your Navigation graph. Just retrieve with `by koinNavGraphViewModel()`. You just need your graph Id. +You can scope a ViewModel instance to your Navigation graph. Just retrieve with `by koinNavGraphViewModel()`. You just need your graph id. ```kotlin class NavFragment : Fragment() { @@ -175,6 +179,10 @@ class NavFragment : Fragment() { } ``` +## ViewModel Scope API + +see all API to be used for ViewModel and Scopes: [ViewModel Scope](/docs/reference/koin-android/scope.md#viewmodel-scope-since-354) + ## ViewModel Generic API Koin provides some "under the hood" API to directly tweak your ViewModel instance. The available functions are `viewModelForClass` for `ComponentActivity` and `Fragment`: diff --git a/docs/reference/koin-android/workmanager.md b/docs/reference/koin-android/workmanager.md index 50acbb03f..eecf8a9d9 100644 --- a/docs/reference/koin-android/workmanager.md +++ b/docs/reference/koin-android/workmanager.md @@ -84,7 +84,7 @@ In case both Koin and workFactory1 provided WorkManagerFactory can instantiate a ## A few assumptions ### Add manifest changes in koin lib itself -We can make it one step less for application developers if koin-androidx-workmanager's own manifest disables the default work manager. However it can be confusing since if the app developer don't initialize koin's work manager infrastructure, he'll end up having no usable work manager factories. +We can make it one step less for application developers if koin-androidx-workmanager's own manifest disables the default work manager. However, it can be confusing since if the app developer don't initialize koin's work manager infrastructure, he'll end up having no usable work manager factories. That's something that checkModules could help: if any class in the project implements ListenableWorker we inspect both manifest and code and make sure they make sense? diff --git a/docs/reference/koin-compose/compose.md b/docs/reference/koin-compose/compose.md index 19b65fcd6..68d6aa22f 100644 --- a/docs/reference/koin-compose/compose.md +++ b/docs/reference/koin-compose/compose.md @@ -6,7 +6,7 @@ This page describe how you can inject your dependencies for your Jetpack Compose ## Starting Koin with Android Jetpack Compose - KoinApplication or KoinAndroidContext -Most of the time, `startKoin` function is used to start Koin in your application. This is done before running any Composable function. You need to setup Compose with your current Koin instance. Use `KoinAndroidContext()` to do so: +Most of the time, `startKoin` function is used to start Koin in your application. This is done before running any Composable function. You need to setting up Compose with your current Koin instance. Use `KoinAndroidContext()` to do so: ```kotlin @Composable @@ -32,12 +32,16 @@ fun App() { } ``` -:::info +:::note Difference between `KoinAndroidContext` and `KoinContext`: - `KoinAndroidContext` is looking into current Android app context for Koin instance - `KoinContext` is looking into current GlobalContext for Koin instances ::: +:::info +If you get some `ClosedScopeException` from a Composable, either use `KoinContext` on your Composable or ensure to have proper Koin start configuration [with Android context](/docs/reference/koin-android/start.md#from-your-application-class) +::: + ### Compose Preview with Koin The `KoinApplication` function is also interesting to start dedicated context for preview. This can be also used to help with Compose preview: diff --git a/docs/reference/koin-compose/isolated-context.md b/docs/reference/koin-compose/isolated-context.md index 384705694..a2a18a6d2 100644 --- a/docs/reference/koin-compose/isolated-context.md +++ b/docs/reference/koin-compose/isolated-context.md @@ -2,7 +2,7 @@ title: Isolated Context with Compose --- -With a Compose application, you can work the same way with an [isolated context](../koin-core/context-isolation.md) to deal with SDK or white label application, in order to not mix your Koin definitions with a end user's one. +With a Compose application, you can work the same way with an [isolated context](/docs/reference/koin-core/context-isolation.md) to deal with SDK or white label application, in order to not mix your Koin definitions with an end user's one. ## Define isolated context @@ -24,7 +24,7 @@ Adapt the `MyIsolatedKoinContext` class according your need of initialization ## Setup isolated context with Compose -Now that you have defined an isolated Koin context, we can setup it up to Compose to use it and override all the API. Just use the `KoinIsolatedContext` at the root Compose function. This will propagate your Koin context in all child composables. +Now that you have defined an isolated Koin context, we can seting up it up to Compose to use it and override all the API. Just use the `KoinIsolatedContext` at the root Compose function. This will propagate your Koin context in all child composables. ```kotlin @Composable diff --git a/docs/reference/koin-compose/multiplatform.md b/docs/reference/koin-compose/multiplatform.md index d6a5f7267..d6c21ee6a 100644 --- a/docs/reference/koin-compose/multiplatform.md +++ b/docs/reference/koin-compose/multiplatform.md @@ -6,7 +6,7 @@ This page describe how you can inject your dependencies for your Jetpack & Jetbr ## Starting Koin with Compose - KoinApplication or KoinContext -Most of the time, `startKoin` function is used to start Koin in your application. This is done before running any Composable function. You need to setup Compose with your current Koin instance. Use `KoinContext()` to do so: +Most of the time, `startKoin` function is used to start Koin in your application. This is done before running any Composable function. You need to seting up Compose with your current Koin instance. Use `KoinContext()` to do so: ```kotlin @Composable @@ -106,7 +106,7 @@ For this use `unloadOnForgotten` or `unloadOnAbandoned` argument for `rememberKo ## Creating Koin Scope with Composable -The composable function `rememberKoinScope` and `KoinScope` allow to handle Koin Scope in a Composable, follow up current to close scope once Composable is ended. +The composable function `rememberKoinScope` and `KoinScope` allow to handle Koin Scope in a Composable, follow-up current to close scope once Composable is ended. :::info this API is still unstable for now diff --git a/docs/reference/koin-core/context-isolation.md b/docs/reference/koin-core/context-isolation.md index e6dd63b74..bdf7dd253 100644 --- a/docs/reference/koin-core/context-isolation.md +++ b/docs/reference/koin-core/context-isolation.md @@ -3,7 +3,7 @@ title: Context Isolation --- -For SDK Makers, you can also work with Koin in a non global way: use Koin for the DI of your library and avoid any conflict by people using your library and Koin by isolating your context. +For SDK Makers, you can also work with Koin in a non-global way: use Koin for the DI of your library and avoid any conflict by people using your library and Koin by isolating your context. In a standard way, we can start Koin like that: diff --git a/docs/reference/koin-core/definitions.md b/docs/reference/koin-core/definitions.md index da7556a67..2fccae2ad 100644 --- a/docs/reference/koin-core/definitions.md +++ b/docs/reference/koin-core/definitions.md @@ -41,7 +41,7 @@ The result type of your lambda is the main type of your component ## Defining a factory -A factory component declaration is a definition that will gives you a *new instance each time* you ask for this definition (this instance is not retained by Koin container, as it won't inject this instance in other definitions later). Use the `factory` function with a lambda expression to build a component. +A factory component declaration is a definition that will provide you a *new instance each time* you ask for this definition (this instance is not retained by Koin container, as it won't inject this instance in other definitions later). Use the `factory` function with a lambda expression to build a component. ```kotlin class Controller() @@ -84,7 +84,7 @@ val myModule = module { ## Definition: binding an interface -A `single` or a `factory` definition use the type from the their given lambda definition: i.e `single { T }` +A `single` or a `factory` definition use the type from their given lambda definition i.e: `single { T }` The matched type of the definition is the only matched type from this expression. Let's take an example with a class and implemented interface: @@ -103,7 +103,7 @@ class ServiceImp() : Service { } ``` -In a Koin module we can use the `as` cast Kotlin operator as follow: +In a Koin module we can use the `as` cast Kotlin operator as follows: ```kotlin val myModule = module { @@ -184,7 +184,7 @@ val service : Service by inject(qualifier = named("default")) `get()` and `by inject()` functions let you specify a definition name if needed. This name is a `qualifier` produced by the `named()` function. -By default Koin will bind a definition by its type or by its name, if the type is already bound to a definition. +By default, Koin will bind a definition by its type or by its name, if the type is already bound to a definition. ```kotlin val myModule = module { diff --git a/docs/reference/koin-core/dsl-update.md b/docs/reference/koin-core/dsl-update.md index 74565c3a9..3efd152c3 100644 --- a/docs/reference/koin-core/dsl-update.md +++ b/docs/reference/koin-core/dsl-update.md @@ -2,7 +2,7 @@ title: Constructor DSL --- -Koin now offer a new kind of DSL keyword that allow you to target a class constructor directly, and avoid to to have type your definition within a lambda expression. +Koin now offer a new kind of DSL keyword that allow you to target a class constructor directly, and avoid to have type your definition within a lambda expression. For a given class `ClassA` with following dependencies: @@ -45,7 +45,7 @@ The following keywords are available to build your definition from constructor: * `scopedOf` - equivalent of `scoped { }` - scoped definition :::info -Be sure to not use any default value in your constructor, as Koin will try to fill every parameter of it. +Be sure to not use any default value in your constructor, as Koin will try to fill every parameter with it. ::: ## DSL Options @@ -71,7 +71,7 @@ Usual options and DSL keywords are available in this lambda: * `binds(listOf(...))` - add types list for given bean definition * `createdAtStart()` - create single instance at Koin start -You can also use `bind` or `binds` operator, without any need of lambda: +You can also to use `bind` or `binds` operator, without any need of lambda: ```kotlin module { @@ -81,7 +81,7 @@ module { ## Injected Parameters -With such kind of declaration, you can still use injected parameters. Koin will look in injected parameters and current dependencies to try inject your constructor. +With such kind of declaration, you can still use injected parameters. Koin will look in injected parameters and current dependencies to try to inject your constructor. Like following: diff --git a/docs/reference/koin-core/dsl.md b/docs/reference/koin-core/dsl.md index 4fc0f12c9..f5f54be31 100644 --- a/docs/reference/koin-core/dsl.md +++ b/docs/reference/koin-core/dsl.md @@ -36,7 +36,7 @@ As you can see above, we can describe a Koin container configuration in 2 ways: - `koinApplication` describe a Koin container instance - `startKoin` describe a Koin container instance and register it in Koin `GlobalContext` -By registering your container configuration into the `GlobalContext`, the global API can use it directly. Any `KoinComponent` refers to a `Koin` instance. By default we use the one from `GlobalContext`. +By registering your container configuration into the `GlobalContext`, the global API can use it directly. Any `KoinComponent` refers to a `Koin` instance. By default, we use the one from `GlobalContext`. Check chapters about Custom Koin instance for more information. @@ -70,7 +70,7 @@ To describe your content in a module, you can use the following functions: * `bind()` - add type to bind for given bean definition * `binds()` - add types array for given bean definition * `scope { // scope group }` - define a logical group for `scoped` definition -* `scoped { //definition }`- provide a bean definition that will exists only in a scope +* `scoped { //definition }`- provide a bean definition that will exist only in a scope Note: the `named()` function allow you to give a qualifier either by a string, an enum or a type. It is used to name your definitions. diff --git a/docs/reference/koin-core/injection-parameters.md b/docs/reference/koin-core/injection-parameters.md index ef0dc04d1..94b3d9f49 100644 --- a/docs/reference/koin-core/injection-parameters.md +++ b/docs/reference/koin-core/injection-parameters.md @@ -57,7 +57,7 @@ val myModule = module { ## Resolving injected parameters in order -Instead of using `get()` to resolve a parameter, if you have several parameters of the same type you can use the index as follow `get(index)` (also same as `[ ]` operator): +Instead of using `get()` to resolve a parameter, if you have several parameters of the same type you can use the index as follows `get(index)` (also same as `[ ]` operator): ```kotlin class Presenter(val view : View) @@ -85,7 +85,7 @@ val myModule = module { In addition to `parametersOf`, the following API are accessible: -- `parameterArrayOf`: to use an array of value, and data will be use by its index +- `parameterArrayOf`: to use an array of value, and data will be uses by its index ```kotlin val params = parameterArrayOf(1,2,3) diff --git a/docs/reference/koin-core/koin-component.md b/docs/reference/koin-core/koin-component.md index 1421bb880..976e70148 100644 --- a/docs/reference/koin-core/koin-component.md +++ b/docs/reference/koin-core/koin-component.md @@ -3,7 +3,7 @@ title: Koin Component --- Koin is a DSL to help describe your modules & definitions, a container to make definition resolution. What we need now is -an API to retrieve our instances outside of the container. That's the goal of Koin components. +an API to retrieve our instances outside the container. That's the goal of Koin components. :::info The `KoinComponent` interface is here to help you retrieve instances directly from Koin. Be careful, this links your class to the Koin container API. Avoid to use it on classes that you can declare in `modules`, and prefer constructor injection diff --git a/docs/reference/koin-core/lazy-modules.md b/docs/reference/koin-core/lazy-modules.md new file mode 100644 index 000000000..e7e945112 --- /dev/null +++ b/docs/reference/koin-core/lazy-modules.md @@ -0,0 +1,66 @@ +--- +title: Lazy Modules and Background Loading +--- + +In this section we will see how to organize your modules with lazy loading approach. + +## Defining Lazy Modules [Experimental] + +You can now declare lazy Koin module, to avoid trigger any pre allocation of resources and load them in background with Koin start. + +- `lazyModule` - declare a Lazy Kotlin version of Koin Module +- `Module.includes` - allow to include lazy Modules + +A good example is always better to understand: + +```kotlin +// Some lazy modules +val m2 = lazyModule { + singleOf(::ClassB) +} + +// include m2 lazy module +val m1 = lazyModule { + includes(m2) + singleOf(::ClassA) { bind() } +} +``` + +:::info + LazyModule won't trigger any resources until it has been loaded by the following API +::: + +## Background loading with Kotlin coroutines [Experimental] + +Once you have declared some lazy modules, you can load them in background from your Koin configuration and further more. + +- `KoinApplication.lazyModules` - load lazy modules in background with coroutines, regarding platform default Dispatchers +- `Koin.waitAllStartJobs` - wait for start jobs to complete +- `Koin.runOnKoinStarted` - run block code after start completion + +A good example is always better to understand: + +```kotlin +startKoin { + // load lazy Modules in background + lazyModules(m1) +} + +val koin = KoinPlatform.getKoin() + +// wait for loading jobs to finish +koin.waitAllStartJobs() + +// or run code after loading is done +koin.runOnKoinStarted { koin -> + // run after background load complete +} +``` + +:::note + The `lazyModules` function allow you to specify a dispatcher: `lazyModules(modules, dispatcher = Dispatcher.IO)` +::: + +:::info + Default dispatcher for coroutines engine is `Dispatchers.Default` +::: \ No newline at end of file diff --git a/docs/reference/koin-core/modules.md b/docs/reference/koin-core/modules.md index c84c5081d..153795168 100644 --- a/docs/reference/koin-core/modules.md +++ b/docs/reference/koin-core/modules.md @@ -17,7 +17,7 @@ val myModule = module { ## Using several modules Components doesn't have to be necessarily in the same module. A module is a logical space to help you organize your definitions, and can depend on definitions from other -module. Definitions are lazy, and then are resolved only when a a component is requesting it. +module. Definitions are lazy, and then are resolved only when a component is requesting it. Let's take an example, with linked components in separate modules: @@ -95,7 +95,7 @@ fun sharedModule() = module { } ``` -This way, your share the definitions and avoid preallocate factories in a value. +This way, you share the definitions and avoid preallocating factories in a value. ## Overriding definition or module (before 3.1.0) @@ -253,45 +253,5 @@ startKoin { modules(featureModule1, featureModule2) } Notice that all modules will be included only once: `dataModule`, `domainModule`, `featureModule1`, `featureModule2`. :::info - If you have any compiling issue while including modules from the same file, either use `get()` Kotlin attribute operator on your module either separate each module in files. See https://github.com/InsertKoinIO/koin/issues/1341 workaround + If you have any compiling issue while including modules from the same file, either use `get()` Kotlin attribute operator on your module or separate each module in files. See https://github.com/InsertKoinIO/koin/issues/1341 workaround ::: - -## Lazy modules & background modules loading with Kotlin coroutines [Experimental] - -You can now declare "lazy" Koin module, to avoid trigger any pre allocation of resources and load them in background with Koin start. - -- `lazyModule` - declare a Lazy Kotlin version of Koin Module -- `Module.includes` - allow to include lazy Modules -- `KoinApplication.lazyModules` - load lazy modules in background with coroutines, regarding platform default Dispatchers -- `Koin.waitAllStartJobs` - wait for start jobs to complete -- `Koin.runOnKoinStarted` - run block code after start completion - -A good example is always better to understand: - -```kotlin -// Some lazy modules -val m2 = lazyModule { - singleOf(::ClassB) -} - -// include m2 lazy module -val m1 = lazyModule { - includes(m2) - singleOf(::ClassA) { bind() } -} - -startKoin { - // load lazy Modules in background - lazyModules(m1) -} - -val koin = KoinPlatform.getKoin() - -// wait for loading jobs to finish -koin.waitAllStartJobs() - -// or run code after loading is done -koin.runOnKoinStarted { koin -> - // run after background load complete -} -``` \ No newline at end of file diff --git a/docs/reference/koin-core/scopes.md b/docs/reference/koin-core/scopes.md index e82bc8dde..a4dea5ec2 100644 --- a/docs/reference/koin-core/scopes.md +++ b/docs/reference/koin-core/scopes.md @@ -12,7 +12,7 @@ When the scope context ends, any objects bound under that scope cannot be inject ## Scope definition -By default in Koin, we have 3 kind of scopes: +By default, in Koin, we have 3 kind of scopes: - `single` definition, create an object that persistent with the entire container lifetime (can't be dropped). - `factory` definition, create a new object each time. Short live. No persistence in the container (can't be shared). diff --git a/docs/reference/koin-core/start-koin.md b/docs/reference/koin-core/start-koin.md index 14ca8b3d0..c780ddeb8 100644 --- a/docs/reference/koin-core/start-koin.md +++ b/docs/reference/koin-core/start-koin.md @@ -3,11 +3,11 @@ title: Start Koin --- -Koin is a DSL, a lightweight container and a pragmatic API. Once you have declared your definitions within Koin modules, your are ready to start the Koin container. +Koin is a DSL, a lightweight container and a pragmatic API. Once you have declared your definitions within Koin modules, you are ready to start the Koin container. ### The startKoin function -The `startKoin` function is the main entry point to launch Koin container. It need a *list of Koin modules* to run. +The `startKoin` function is the main entry point to launch Koin container. It needs a *list of Koin modules* to run. Modules are loaded and definitions are ready to be resolved by the Koin container. .Starting Koin diff --git a/docs/reference/koin-ktor/ktor-isolated.md b/docs/reference/koin-ktor/ktor-isolated.md index 8312ba673..b4481244a 100644 --- a/docs/reference/koin-ktor/ktor-isolated.md +++ b/docs/reference/koin-ktor/ktor-isolated.md @@ -20,7 +20,7 @@ fun Application.main() { ``` :::warning - By using an isolated Koin context you won't be able to use Koin outside of Ktor server instance (i.e: by using `GlobalContext` for example) + By using an isolated Koin context you won't be able to use Koin outside Ktor server instance (i.e: by using `GlobalContext` for example) ::: diff --git a/docs/reference/koin-mp/kmp.md b/docs/reference/koin-mp/kmp.md index d5600e7ad..c0348a3e1 100644 --- a/docs/reference/koin-mp/kmp.md +++ b/docs/reference/koin-mp/kmp.md @@ -93,13 +93,13 @@ fun initKoin(){ } ``` -We can init it in our Main app entry: +We can initialize it in our Main app entry: ```kotlin @main struct iOSApp: App { - // KMM - Koin Call + // KMM - Koin Call init() { HelperKt.doInitKoin() } diff --git a/docs/reference/koin-test/checkmodules.md b/docs/reference/koin-test/checkmodules.md index 67e87494c..ad3ea4023 100644 --- a/docs/reference/koin-test/checkmodules.md +++ b/docs/reference/koin-test/checkmodules.md @@ -3,7 +3,7 @@ title: Verifying your Koin configuration --- :::note -Koin allows you to verify your configuration modules, avoiding to discover dependency injection issues at runtime. +Koin allows you to verify your configuration modules, avoiding discovering dependency injection issues at runtime. ::: @@ -252,7 +252,7 @@ fun `test DI modules`(){ } ``` -All injected definition that are using a injected `String` parameter, will receive `"_ID_"`: +All injected definition that are using an injected `String` parameter, will receive `"_ID_"`: ```kotlin module { diff --git a/docs/reference/koin-test/testing.md b/docs/reference/koin-test/testing.md index 52618c5f0..f3b272681 100644 --- a/docs/reference/koin-test/testing.md +++ b/docs/reference/koin-test/testing.md @@ -4,7 +4,7 @@ title: Injecting in Tests ## Making your test a KoinComponent with KoinTest -*Warning*: This does not apply to Android Instrumented tests. For Instrumented testing with Koin, please see [Android Instrumented Testing](../koin-android/instrumented-testing.md) +*Warning*: This does not apply to Android Instrumented tests. For Instrumented testing with Koin, please see [Android Instrumented Testing](/docs/reference/koin-android/instrumented-testing.md) By tagging your class `KoinTest`, your class become a `KoinComponent` and bring you: @@ -123,7 +123,7 @@ class MyTest : KoinTest { ``` :::note - declareMock can specify if you want a single or factory, and if you wan to have it in a module path. + declareMock can specify if you want a single or factory, and if you want to have it in a module path. ::: ## Declaring a component on the fly diff --git a/docs/resources/articles.md b/docs/resources/articles.md index 323c74340..5f042cbe8 100644 --- a/docs/resources/articles.md +++ b/docs/resources/articles.md @@ -6,7 +6,7 @@ title: Articles * [Testing a Koin application with KotlinTest](https://dev.to/kerooker/testing-koin-applications-with-kotlintest-1iip) * [Ready for Koin 2.0](https://medium.com/koin-developers/ready-for-koin-2-0-2722ab59cac3) * [Migration from Dagger2 to Koin](https://proandroiddev.com/migrating-from-dagger2-to-koin-3b2b3f5285e9) -* [From Dagger to Koin, a step by step migration guide](https://medium.com/@giuliani.arnaud/the-thermosiphon-app-from-dagger-to-koin-step-by-step-a09af7f5b5b1) +* [From Dagger to Koin, a step-by-step migration guide](https://medium.com/@giuliani.arnaud/the-thermosiphon-app-from-dagger-to-koin-step-by-step-a09af7f5b5b1) * [Koin in Feature Modules Project](https://proandroiddev.com/koin-in-feature-modules-project-6329f069f943) * [A brief look at Koin on Android](https://overflow.buffer.com/2018/09/13/a-brief-look-at-koin-on-android/) * [Bye bye Dagger](https://medium.com/@charbgr/bye-bye-dagger-1494118dcd41) diff --git a/docs/setup/koin.md b/docs/setup/koin.md index 66c3e572e..a1ba5e9c5 100644 --- a/docs/setup/koin.md +++ b/docs/setup/koin.md @@ -2,7 +2,7 @@ title: Koin --- -All you need to setup Koin in your project +All you need to setting up Koin in your project ## Current Versions @@ -10,21 +10,22 @@ You can find all Koin packages on [maven central](https://search.maven.org/searc Here are the currently available versions: -| Project | Version | -|----------|:-------------:| -| koin-core | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-core)](https://mvnrepository.com/artifact/io.insert-koin/koin-core) | -| koin-core-coroutines | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-core-coroutines)](https://mvnrepository.com/artifact/io.insert-koin/koin-core-coroutines) | -| koin-test | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-test)](https://mvnrepository.com/artifact/io.insert-koin/koin-test) | -| koin-android | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-android)](https://mvnrepository.com/artifact/io.insert-koin/koin-android) | -| koin-android-test | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-android-test)](https://mvnrepository.com/artifact/io.insert-koin/koin-android-test) | -| koin-android-compat | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-android-compat)](https://mvnrepository.com/artifact/io.insert-koin/koin-android-compat) | -| koin-androidx-navigation | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-androidx-navigation)](https://mvnrepository.com/artifact/io.insert-koin/koin-androidx-navigation) | -| koin-androidx-workmanager | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-androidx-workmanager)](https://mvnrepository.com/artifact/io.insert-koin/koin-androidx-workmanager) | -| koin-compose | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-compose)](https://mvnrepository.com/artifact/io.insert-koin/koin-compose) | -| koin-androidx-compose | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-androidx-compose)](https://mvnrepository.com/artifact/io.insert-koin/koin-androidx-compose) | -| koin-androidx-compose-navigation | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-androidx-compose-navigation)](https://mvnrepository.com/artifact/io.insert-koin/koin-androidx-compose-navigation) | -| koin-ktor | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-ktor)](https://mvnrepository.com/artifact/io.insert-koin/koin-ktor) | -| koin-logger-slf4j | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-logger-slf4j)](https://mvnrepository.com/artifact/io.insert-koin/koin-logger-slf4j) | +| Project | Version | +|----------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| +| koin-bom | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-bom)](https://mvnrepository.com/artifact/io.insert-koin/koin-bom) | +| koin-core | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-core)](https://mvnrepository.com/artifact/io.insert-koin/koin-core) | +| koin-core-coroutines | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-core-coroutines)](https://mvnrepository.com/artifact/io.insert-koin/koin-core-coroutines) | +| koin-test | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-test)](https://mvnrepository.com/artifact/io.insert-koin/koin-test) | +| koin-android | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-android)](https://mvnrepository.com/artifact/io.insert-koin/koin-android) | +| koin-android-test | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-android-test)](https://mvnrepository.com/artifact/io.insert-koin/koin-android-test) | +| koin-android-compat | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-android-compat)](https://mvnrepository.com/artifact/io.insert-koin/koin-android-compat) | +| koin-androidx-navigation | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-androidx-navigation)](https://mvnrepository.com/artifact/io.insert-koin/koin-androidx-navigation) | +| koin-androidx-workmanager | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-androidx-workmanager)](https://mvnrepository.com/artifact/io.insert-koin/koin-androidx-workmanager) | +| koin-compose | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-compose)](https://mvnrepository.com/artifact/io.insert-koin/koin-compose) | +| koin-androidx-compose | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-androidx-compose)](https://mvnrepository.com/artifact/io.insert-koin/koin-androidx-compose) | +| koin-androidx-compose-navigation | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-androidx-compose-navigation)](https://mvnrepository.com/artifact/io.insert-koin/koin-androidx-compose-navigation) | +| koin-ktor | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-ktor)](https://mvnrepository.com/artifact/io.insert-koin/koin-ktor) | +| koin-logger-slf4j | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-logger-slf4j)](https://mvnrepository.com/artifact/io.insert-koin/koin-logger-slf4j) | ## Gradle Setup @@ -44,7 +45,7 @@ koin-bom = "x.x.x" ... [libraries] -koin-bom = { module = "io.insert-koin:koin-bom", version.ref = "di-koin" } +koin-bom = { module = "io.insert-koin:koin-bom", version.ref = "koin-bom" } koin-core = { module = "io.insert-koin:koin-core" } ... ``` @@ -86,7 +87,7 @@ dependencies { ``` :::info -From now you can continue on Koin Tutorials to learn about using Koin: [Kotlin App Tutorial](../quickstart/kotlin) +From now you can continue on Koin Tutorials to learn about using Koin: [Kotlin App Tutorial](/docs/quickstart/kotlin) ::: ### **Android** @@ -127,7 +128,7 @@ dependencies { ``` :::info -From now you can continue on Koin Tutorials to learn about using Koin: [Android App Tutorial](../quickstart/android-viewmodel) +From now you can continue on Koin Tutorials to learn about using Koin: [Android App Tutorial](/docs/quickstart/android-viewmodel) ::: ### **Android Jetpack Compose** @@ -153,7 +154,7 @@ class MainApplication : Application() { ``` :::info -From now you can continue on Koin Tutorials to learn about using Koin: [Android Compose App Tutorial](../quickstart/android-compose) +From now you can continue on Koin Tutorials to learn about using Koin: [Android Compose App Tutorial](/docs/quickstart/android-compose) ::: @@ -168,7 +169,7 @@ dependencies { ``` :::info -From now you can continue on Koin Tutorials to learn about using Koin: [Kotlin Multiplatform App Tutorial](../quickstart/kmm) +From now you can continue on Koin Tutorials to learn about using Koin: [Kotlin Multiplatform App Tutorial](/docs/quickstart/kmm) ::: ### **Ktor** @@ -196,5 +197,28 @@ fun Application.main() { ``` :::info -From now you can continue on Koin Tutorials to learn about using Koin: [Ktor App Tutorial](../quickstart/ktor) +From now you can continue on Koin Tutorials to learn about using Koin: [Ktor App Tutorial](/docs/quickstart/ktor) ::: + + +### **Koin BOM** +The Koin Bill of Materials (BOM) lets you manage all of your Koin library versions by specifying only the BOM’s version. The BOM itself has links to the stable versions of the different Koin libraries, in such a way that they work well together. When using the BOM in your app, you don't need to add any version to the Koin library dependencies themselves. When you update the BOM version, all the libraries that you're using are automatically updated to their new versions. + +```groovy +dependencies { + // Declare koin-bom version + implementation platform("io.insert-koin:koin-bom:$koin_bom") + + // Declare the koin dependencies that you need + implementation("io.insert-koin:koin-android") + implementation("io.insert-koin:koin-core-coroutines") + implementation("io.insert-koin:koin-androidx-workmanager") + + // If you need specify some version it's just point to desired version + implementation("io.insert-koin:koin-androidx-navigation:1.2.3-alpha03") + + // Works with test libraries too! + testImplementation("io.insert-koin:koin-test-junit4") + testImplementation("io.insert-koin:koin-android-test") +} +``` diff --git a/docs/setup/why.md b/docs/setup/why.md index 3134d13ca..2a2ec43dc 100644 --- a/docs/setup/why.md +++ b/docs/setup/why.md @@ -2,7 +2,7 @@ title: Why Koin? --- -Koin provides a easy and efficient way to incorporate dependency injection into any Kotlin application(Multiplatform, Android, backend ...) +Koin provides an easy and efficient way to incorporate dependency injection into any Kotlin application(Multiplatform, Android, backend ...) The goals of Koin are: - Simplify your Dependency Injection infrastructure with smart API diff --git a/docs/upgrade/migrate.md b/docs/upgrade/migrate.md index 022da2406..44dbbd86e 100644 --- a/docs/upgrade/migrate.md +++ b/docs/upgrade/migrate.md @@ -10,7 +10,7 @@ The new branch of Koin 3.x is bringing latest features & fixes. It brings Multip #### Android & AndroidX Module -The following modules are available for Android. Be sure to use the `koin-androidx` modules, as their are based on the latest AndroidX library support: +The following modules are available for Android. Be sure to use the `koin-androidx` modules, as there are based on the latest AndroidX library support: ```groovy // Koin AndroidX Scope features @@ -33,7 +33,7 @@ The new Koin core is now Multiplatform. You can use it even if your project is n #### Reimporting for new API -If you seen unresolved Koin API import, you can try to remove/reimport the used API as some packages has been moved in the Koin core module. See new section +If you have seen unresolved Koin API import, you can try to remove/reimport the used API as some packages has been moved in the Koin core module. See new section #### Kotlin Core & JVM features @@ -57,7 +57,7 @@ Java/JVM Specific API are isolated in `koin-core-jvm` part of Koin core. Here ar #### Android Modules -In v3, the `koin-android` modules is using AndroidX API, and merge also all Scope/Fragment/ViewModel API. You don't need anymore to specify `koin-androidx-fragment`, `koin-androidx-viewmodel` or `koin-androidx-scope` as their are all in the `koin-android` module: +In v3, the `koin-android` modules is using AndroidX API, and merge also all Scope/Fragment/ViewModel API. You don't need anymore to specify `koin-androidx-fragment`, `koin-androidx-viewmodel` or `koin-androidx-scope` as there are all in the `koin-android` module: ```groovy // Koin main features for Android (Scope,ViewModel ...) diff --git a/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/components/mvvm/ExtSimpleViewModel.kt b/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/components/mvvm/ExtSimpleViewModel.kt deleted file mode 100644 index bd8b7d75d..000000000 --- a/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/components/mvvm/ExtSimpleViewModel.kt +++ /dev/null @@ -1,6 +0,0 @@ -package org.koin.sample.sandbox.components.mvvm - -import androidx.lifecycle.ViewModel -import org.koin.sample.sandbox.components.scope.Session - -class ExtSimpleViewModel(val session: Session) : ViewModel() \ No newline at end of file diff --git a/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/components/mvvm/MyScopeViewModel.kt b/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/components/mvvm/MyScopeViewModel.kt new file mode 100644 index 000000000..e4953becd --- /dev/null +++ b/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/components/mvvm/MyScopeViewModel.kt @@ -0,0 +1,19 @@ +package org.koin.sample.sandbox.components.mvvm + +import androidx.lifecycle.ViewModel +import org.koin.core.component.KoinScopeComponent +import org.koin.core.component.createScope +import org.koin.core.scope.Scope +import org.koin.sample.sandbox.components.scope.Session + +class MyScopeViewModel : ViewModel(), KoinScopeComponent { + + override val scope: Scope = createScope(this) + + val session by scope.inject() + + override fun onCleared() { + super.onCleared() + scope.close() + } +} \ No newline at end of file diff --git a/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/components/mvvm/MyScopeViewModel2.kt b/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/components/mvvm/MyScopeViewModel2.kt new file mode 100644 index 000000000..c94f2c574 --- /dev/null +++ b/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/components/mvvm/MyScopeViewModel2.kt @@ -0,0 +1,12 @@ +package org.koin.sample.sandbox.components.mvvm + +import org.koin.androidx.scope.ScopeViewModel +import org.koin.core.annotation.KoinExperimentalAPI +import org.koin.sample.sandbox.components.scope.Session + +@OptIn(KoinExperimentalAPI::class) +class MyScopeViewModel2 : ScopeViewModel() { + + val session by scope.inject() + +} \ No newline at end of file diff --git a/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/components/mvvm/SavedStateViewModel.kt b/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/components/mvvm/SavedStateViewModel.kt index 8bf281ee0..cbd2b4d00 100644 --- a/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/components/mvvm/SavedStateViewModel.kt +++ b/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/components/mvvm/SavedStateViewModel.kt @@ -9,6 +9,6 @@ class SavedStateViewModel(val handle: SavedStateHandle, val id: String, val serv init { val get = handle.get(id) println("handle: $get") - handle.set(id,UUID.randomUUID().toString()) + handle[id] = UUID.randomUUID().toString() } } \ No newline at end of file diff --git a/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/components/scope/Session.kt b/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/components/scope/Session.kt index c1813767d..be37d41c7 100644 --- a/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/components/scope/Session.kt +++ b/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/components/scope/Session.kt @@ -6,6 +6,12 @@ class Session { var id: String = UUID.randomUUID().toString() } +class SessionConsumer(private val session: Session){ + + fun getSessionId() = session.id + +} + class SessionActivity { val id: String = UUID.randomUUID().toString() } diff --git a/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/di/AppModule.kt b/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/di/AppModule.kt index 3d244163d..2c531becb 100644 --- a/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/di/AppModule.kt +++ b/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/di/AppModule.kt @@ -19,6 +19,7 @@ import org.koin.sample.sandbox.components.mvp.ScopedPresenter import org.koin.sample.sandbox.components.mvvm.* import org.koin.sample.sandbox.components.scope.Session import org.koin.sample.sandbox.components.scope.SessionActivity +import org.koin.sample.sandbox.components.scope.SessionConsumer import org.koin.sample.sandbox.mvp.MVPActivity import org.koin.sample.sandbox.mvvm.MVVMActivity import org.koin.sample.sandbox.mvvm.MVVMFragment @@ -43,13 +44,13 @@ val mvpModule = lazyModule { scope { scopedOf(::ScopedPresenter)// { (id: String) -> ScopedPresenter(id, get()) } + } } val mvvmModule = lazyModule { viewModelOf(::SimpleViewModel)// { (id: String) -> SimpleViewModel(id, get()) } - viewModelOf(::SimpleViewModel) { named("vm1") } //{ (id: String) -> SimpleViewModel(id, get()) } viewModel(named("vm2")) { (id: String) -> SimpleViewModel(id, get()) } @@ -59,22 +60,30 @@ val mvvmModule = lazyModule { // viewModel { ViewModelImpl(get()) } viewModelOf(::ViewModelImpl) { bind() } - scope { + viewModelOf(::MyScopeViewModel) + scope { + scopedOf(::Session) + } + viewModelOf(::MyScopeViewModel2) + scope { + scopedOf(::Session) + scopedOf(::SessionConsumer) + } + + viewModelOf(::SavedStateViewModel) { named("vm2") } + + scope { scopedOf(::Session) fragmentOf(::MVVMFragment) // { MVVMFragment(get()) } - viewModelOf(::ExtSimpleViewModel) - viewModelOf(::ExtSimpleViewModel) { named("ext") } - viewModelOf(::SavedStateViewModel) { named("vm2") } scoped { MVVMPresenter1(get()) } scoped { MVVMPresenter2(get()) } } scope { scoped { (id: String) -> ScopedPresenter(id, get()) } + // to retrieve from parent // scopedOf(::Session) - viewModelOf(::ExtSimpleViewModel) - viewModelOf(::ExtSimpleViewModel) { named("ext") } } } diff --git a/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/mvvm/MVVMActivity.kt b/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/mvvm/MVVMActivity.kt index 9da57428c..4372212b3 100644 --- a/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/mvvm/MVVMActivity.kt +++ b/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/mvvm/MVVMActivity.kt @@ -8,35 +8,28 @@ import org.koin.android.ext.android.inject import org.koin.androidx.fragment.android.replace import org.koin.androidx.fragment.android.setupKoinFragmentFactory import org.koin.androidx.scope.ScopeActivity -import org.koin.androidx.viewmodel.ext.android.getViewModel import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.androidx.viewmodel.ext.android.viewModelForClass import org.koin.core.parameter.parametersOf import org.koin.core.qualifier.named import org.koin.sample.sandbox.R -import org.koin.sample.sandbox.components.ID import org.koin.sample.sandbox.components.mvp.FactoryPresenter import org.koin.sample.sandbox.components.mvvm.* import org.koin.sample.sandbox.components.scope.Session +import org.koin.sample.sandbox.components.scope.SessionConsumer import org.koin.sample.sandbox.scope.ScopedActivityA import org.koin.sample.sandbox.utils.navigateTo class MVVMActivity : ScopeActivity(contentLayoutId = R.layout.mvvm_activity) { - lateinit var simpleViewModel: SimpleViewModel //by viewModel { parametersOf(ID) } - + val vm: SimpleViewModel by viewModel { parametersOf("vm") } val vm1: SimpleViewModel by viewModel(named("vm1")) { parametersOf("vm1") } val vm2: SimpleViewModel by viewModel(named("vm2")) { parametersOf("vm2") } - val scopeVm: ExtSimpleViewModel by viewModel() - val extScopeVm: ExtSimpleViewModel by viewModel(named("ext")) - - // val savedVm: SavedStateViewModel by stateViewModel { parametersOf("vm1") } - val savedVm: SavedStateViewModel by viewModel { parametersOf("vm1") } + val scopeVm1: MyScopeViewModel by viewModel() + val scopeVm2: MyScopeViewModel2 by viewModel() - // val state = Bundle().apply { putString("id", "vm1") } -// val stateVM: SavedStateBundleViewModel by stateViewModel(state = { state }) val stateVM: SavedStateBundleViewModel by viewModel() + val savedVm: SavedStateViewModel by viewModel { parametersOf("vm1") } val abstractVM : AbstractViewModel by viewModel() @@ -60,8 +53,6 @@ class MVVMActivity : ScopeActivity(contentLayoutId = R.layout.mvvm_activity) { navigateTo(isRoot = true) } -// simpleViewModel = viewModelForClass(SimpleViewModel::class, owner = this).value - checks() } @@ -77,14 +68,18 @@ class MVVMActivity : ScopeActivity(contentLayoutId = R.layout.mvvm_activity) { private fun checks() { assert(abstractVM is ViewModelImpl) - assert(scopeVm.session.id == extScopeVm.session.id) assert(stateVM.result == "vm1") + assert(vm1.id != vm.id) assert(vm1.id != vm2.id) val p1 = scope.get() val p2 = scope.get() - assert(p1.ctx == this) assert(p2.ctx == getKoin().get()) + + assert(scopeVm1.session.id == scopeVm1.scope.get().id) + assert(scopeVm2.session.id == scopeVm2.scope.get().id) + assert(scopeVm2.scope.get().getSessionId() == scopeVm2.scope.get().id) + assert(scopeVm1.session.id != scopeVm2.session.id) } } \ No newline at end of file diff --git a/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/mvvm/MVVMFragment.kt b/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/mvvm/MVVMFragment.kt index a816ca157..2137d06fb 100644 --- a/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/mvvm/MVVMFragment.kt +++ b/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/mvvm/MVVMFragment.kt @@ -11,13 +11,10 @@ import org.koin.androidx.scope.requireScopeActivity import org.koin.androidx.viewmodel.ext.android.activityViewModel import org.koin.androidx.viewmodel.ext.android.getActivityViewModel import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.androidx.viewmodel.ext.android.viewModelForClass import org.koin.core.parameter.parametersOf -import org.koin.core.qualifier.named import org.koin.core.scope.Scope import org.koin.sample.sandbox.R import org.koin.sample.sandbox.components.ID -import org.koin.sample.sandbox.components.mvvm.ExtSimpleViewModel import org.koin.sample.sandbox.components.mvvm.SavedStateViewModel import org.koin.sample.sandbox.components.mvvm.SimpleViewModel import org.koin.sample.sandbox.components.scope.Session @@ -27,14 +24,8 @@ class MVVMFragment(private val session: Session) : Fragment(R.layout.mvvm_fragme override val scope: Scope by fragmentScope() val simpleViewModel: SimpleViewModel by viewModel { parametersOf(ID) } - - // Generic KClass Access - val scopeVm: ExtSimpleViewModel by viewModelForClass(ExtSimpleViewModel::class) - val extScopeVm: ExtSimpleViewModel by viewModel(named("ext")) - val shared: SimpleViewModel by activityViewModel { parametersOf(ID) } - val sharedSaved: SavedStateViewModel by activityViewModel { parametersOf(ID) } val saved by viewModel { parametersOf(ID) } val saved2 by viewModel { parametersOf(ID) } @@ -48,13 +39,8 @@ class MVVMFragment(private val session: Session) : Fragment(R.layout.mvvm_fragme checkNotNull(session) assert(shared != simpleViewModel) - // TODO Handle shared isntance - out of Scope -// assert((requireActivity() as MVVMActivity).simpleViewModel == shared) -// assert((requireActivity() as MVVMActivity).savedVm == sharedSaved) - assert((requireActivity() as MVVMActivity).savedVm != saved) assert((requireActivity() as MVVMActivity).savedVm != saved2) - assert(scopeVm.session.id == extScopeVm.session.id) val shared2 = getActivityViewModel { parametersOf(ID) } diff --git a/examples/gradle/versions.gradle b/examples/gradle/versions.gradle index b71de1cf8..a73946b26 100644 --- a/examples/gradle/versions.gradle +++ b/examples/gradle/versions.gradle @@ -2,8 +2,10 @@ ext { // Kotlin kotlin_version = '1.9.21' // Koin Versions - koin_version = '3.6.0-alpha1' + koin_version = '3.5.4-RC1' koin_android_version = koin_version + koin_compose_version = "1.1.3-RC1" + coroutines_version = "1.7.3" ktor_version = "2.3.7" // Compose diff --git a/examples/sample-desktop-compose/build.gradle.kts b/examples/sample-desktop-compose/build.gradle.kts index 986c5b913..f8a070ef0 100755 --- a/examples/sample-desktop-compose/build.gradle.kts +++ b/examples/sample-desktop-compose/build.gradle.kts @@ -7,11 +7,12 @@ plugins { repositories { mavenCentral() + mavenLocal() maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") google() } -val koin_version : String by project +val koin_compose_version : String by project dependencies { // Note, if you develop a library, you should use compose.desktop.common. @@ -19,7 +20,7 @@ dependencies { // (in a separate module for demo project and in testMain). // With compose.desktop.common you will also lose @Preview functionality implementation(compose.desktop.currentOs) - implementation("io.insert-koin:koin-compose:$koin_version") + implementation("io.insert-koin:koin-compose:$koin_compose_version") } compose.desktop { diff --git a/projects/android/koin-android-compat/src/main/java/org/koin/android/compat/GetViewModelCompat.kt b/projects/android/koin-android-compat/src/main/java/org/koin/android/compat/GetViewModelCompat.kt index 738e275bd..01dc06f0f 100644 --- a/projects/android/koin-android-compat/src/main/java/org/koin/android/compat/GetViewModelCompat.kt +++ b/projects/android/koin-android-compat/src/main/java/org/koin/android/compat/GetViewModelCompat.kt @@ -27,6 +27,7 @@ import org.koin.core.scope.Scope import kotlin.reflect.KClass @OptIn(KoinInternalApi::class) +@Deprecated("scope is not used for ViewModel creation. This will fallback to root scope.") @KoinInternalApi fun resolveViewModelCompat( vmClass: Class, diff --git a/projects/android/koin-android/src/main/java/org/koin/androidx/scope/ScopeViewModel.kt b/projects/android/koin-android/src/main/java/org/koin/androidx/scope/ScopeViewModel.kt new file mode 100644 index 000000000..917ca6f05 --- /dev/null +++ b/projects/android/koin-android/src/main/java/org/koin/androidx/scope/ScopeViewModel.kt @@ -0,0 +1,34 @@ +package org.koin.androidx.scope + +import androidx.lifecycle.ViewModel +import org.koin.core.annotation.KoinExperimentalAPI +import org.koin.core.component.KoinScopeComponent +import org.koin.core.component.createScope +import org.koin.core.scope.Scope + +/** + * Class to help support Koin Scope in a ViewModel + * create directly a scope instance for current ViewModel + * + * allow to intercept before scope closing with `onCloseScope`, to be overriden + * + * Destroy linked scope with `onCleared` + * + * @author Arnaud Giuliani + */ +@KoinExperimentalAPI +abstract class ScopeViewModel : ViewModel(), KoinScopeComponent { + + override val scope: Scope = createScope(this) + + /** + * To override to add behavior before closing Scope + */ + open fun onCloseScope(){} + + override fun onCleared() { + super.onCleared() + onCloseScope() + scope.close() + } +} \ No newline at end of file diff --git a/projects/android/koin-android/src/main/java/org/koin/androidx/viewmodel/GetViewModel.kt b/projects/android/koin-android/src/main/java/org/koin/androidx/viewmodel/GetViewModel.kt index 1e8a9ac1c..c5725d985 100644 --- a/projects/android/koin-android/src/main/java/org/koin/androidx/viewmodel/GetViewModel.kt +++ b/projects/android/koin-android/src/main/java/org/koin/androidx/viewmodel/GetViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelStore import androidx.lifecycle.viewmodel.CreationExtras import org.koin.androidx.viewmodel.factory.KoinViewModelFactory +import org.koin.core.Koin import org.koin.core.annotation.KoinInternalApi import org.koin.core.parameter.ParametersDefinition import org.koin.core.parameter.ParametersHolder @@ -25,6 +26,7 @@ import kotlin.reflect.KClass * @param parameters - for instance building injection */ @KoinInternalApi +@Deprecated("scope is not used for ViewModel creation. This will fallback to root scope.") fun resolveViewModel( vmClass: KClass, viewModelStore: ViewModelStore, @@ -33,11 +35,38 @@ fun resolveViewModel( qualifier: Qualifier? = null, scope: Scope, parameters: ParametersDefinition? = null, +): T { + return resolveViewModel(vmClass, viewModelStore, key, extras, qualifier, scope.getKoin(), parameters) +} + +/** + * Resolve a ViewModel instance + * + * @param vmClass + * @param viewModelStore + * @param key + * @param extras - @see CreationExtras + * @param qualifier + * @param koin + * @param parameters - for instance building injection + */ +@KoinInternalApi +fun resolveViewModel( + vmClass: KClass, + viewModelStore: ViewModelStore, + key: String? = null, + extras: CreationExtras, + qualifier: Qualifier? = null, + koin : Koin, + parameters: ParametersDefinition? = null, ): T { val modelClass: Class = vmClass.java - val factory = KoinViewModelFactory(vmClass, scope, qualifier, parameters) + val factory = KoinViewModelFactory(vmClass, koin.scopeRegistry.rootScope, qualifier, parameters) val provider = ViewModelProvider(viewModelStore, factory, extras) - val vmKey = getViewModelKey(qualifier, scope, key) + val vmKey = getViewModelKey(qualifier, key) + + //To help track Keys +// koin.logger.debug("[vm_key] - provider:$provider - class:$modelClass = $vmKey (q:'${qualifier?.value}', k:'$key')") return when { vmKey != null -> provider[vmKey, modelClass] else -> provider[modelClass] @@ -45,14 +74,11 @@ fun resolveViewModel( } @KoinInternalApi -internal fun getViewModelKey(qualifier: Qualifier?, scope: Scope, key: String?): String? { - return if (qualifier == null && key == null && scope.isRoot) { - null - } else { - val q = qualifier?.value ?: "" - val k = key ?: "" - val s = if (!scope.isRoot) scope.id else "" - "$q$k$s" +internal fun getViewModelKey(qualifier: Qualifier?, key: String?): String? { + return when { + qualifier != null -> qualifier.value + (key?.let { "_$it" } ?: "") + key != null -> key + else -> null } } diff --git a/projects/android/koin-android/src/main/java/org/koin/androidx/viewmodel/ext/android/ViewModelLazy.kt b/projects/android/koin-android/src/main/java/org/koin/androidx/viewmodel/ext/android/ViewModelLazy.kt index 56350c0d2..f1619491a 100644 --- a/projects/android/koin-android/src/main/java/org/koin/androidx/viewmodel/ext/android/ViewModelLazy.kt +++ b/projects/android/koin-android/src/main/java/org/koin/androidx/viewmodel/ext/android/ViewModelLazy.kt @@ -68,6 +68,7 @@ fun Fragment.viewModelForClass( } @OptIn(KoinInternalApi::class) +@Deprecated("scope is not used for ViewModel creation. This will fallback to root scope.") @MainThread fun getLazyViewModelForClass( clazz: KClass, diff --git a/projects/android/koin-android/src/test/java/org/koin/test/android/viewmodel/ViewModelKeyTest.kt b/projects/android/koin-android/src/test/java/org/koin/test/android/viewmodel/ViewModelKeyTest.kt index 7882d5b87..54a708ded 100644 --- a/projects/android/koin-android/src/test/java/org/koin/test/android/viewmodel/ViewModelKeyTest.kt +++ b/projects/android/koin-android/src/test/java/org/koin/test/android/viewmodel/ViewModelKeyTest.kt @@ -13,32 +13,20 @@ class ViewModelKeyTest { @OptIn(KoinInternalApi::class) @Test fun generate_right_key() { - val koin = koinApplication().koin - val root = koin.scopeRegistry.rootScope - val q = StringQualifier("_qualifier_") - val scope = Scope(StringQualifier("_q_"), id = "_id_", _koin = koin, isRoot = false) val key = "_KEY_" assertEquals( - null, getViewModelKey(qualifier = null, scope = root, key = null) - ) - assertEquals( - q.value, getViewModelKey(qualifier = q, scope = root, key = null) + null, getViewModelKey(qualifier = null, key = null) ) assertEquals( - key, getViewModelKey(qualifier = null, scope = root, key = key) + q.value, getViewModelKey(qualifier = q, key = null) ) assertEquals( - scope.id, getViewModelKey(qualifier = null, scope = scope, key = null) + q.value + "_$key", getViewModelKey(qualifier = q, key = key) ) - - assertEquals( - key + scope.id, getViewModelKey(qualifier = null, scope = scope, key = key) - ) - assertEquals( - q.value + key + scope.id, getViewModelKey(qualifier = q, scope = scope, key = key) + key, getViewModelKey(qualifier = null, key = key) ) } } \ No newline at end of file diff --git a/projects/android/koin-android/src/test/java/org/koin/test/androidx/viewmodel/GetViewModelTest.kt b/projects/android/koin-android/src/test/java/org/koin/test/androidx/viewmodel/GetViewModelTest.kt new file mode 100644 index 000000000..5cc338b02 --- /dev/null +++ b/projects/android/koin-android/src/test/java/org/koin/test/androidx/viewmodel/GetViewModelTest.kt @@ -0,0 +1,114 @@ +package org.koin.test.androidx.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelStore +import androidx.lifecycle.viewmodel.CreationExtras +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.junit.Assert.assertNotNull +import org.junit.Test +import org.koin.androidx.viewmodel.resolveViewModel +import org.koin.core.annotation.KoinInternalApi +import org.koin.core.qualifier.Qualifier +import org.koin.core.scope.Scope +import org.koin.dsl.koinApplication + +@KoinInternalApi +class GetViewModelTest { + + private val viewModelStore: ViewModelStore = mockk(relaxed = true) + private val extras: CreationExtras = mockk() + private val qualifier: Qualifier = mockk() + private val koin = koinApplication { }.koin + private val scope: Scope = koin.scopeRegistry.rootScope + + @Test + fun `should return a ViewModel with a default Android key`() { + val classToTest = ViewModel::class + + val firstViewModel = resolveViewModel( + classToTest, + viewModelStore, + null, + extras, + null, + scope, + null + ) + + val expectedKey = "$DEFAULT_ANDROID_VIEW_MODEL_KEY:${classToTest.qualifiedName}" + + assertNotNull(firstViewModel) + verify { viewModelStore[expectedKey] } + } + + @Test + fun `should return a ViewModel considering a specific key, qualifier and a non root scope`() { + val classToTest = ViewModel::class + val qualifierName = "qualifier" + val key = "key" + + every { qualifier.value } returns qualifierName + + val viewModel = resolveViewModel( + classToTest, + viewModelStore, + key, + extras, + qualifier, + scope, + null + ) + + val koinVmKey = "${qualifierName}_$key" + + assertNotNull(viewModel) + verify { viewModelStore[koinVmKey] } + } + + @Test + fun `should return a ViewModel considering a key and a root scope identifier`() { + val classToTest = ViewModel::class + val key = "key" + + val viewModel = resolveViewModel( + classToTest, + viewModelStore, + key, + extras, + null, + scope, + null + ) + + assertNotNull(viewModel) + verify { viewModelStore[key] } + } + + @Test + fun `should return a ViewModel considering a qualifier and a root scope identifier`() { + val classToTest = ViewModel::class + val qualifierValue = "qualifier" + + every { qualifier.value } returns qualifierValue + + val viewModel = resolveViewModel( + classToTest, + viewModelStore, + null, + extras, + qualifier, + scope, + null + ) + + assertNotNull(viewModel) + verify { viewModelStore[qualifierValue] } + } + + companion object { + const val DEFAULT_ANDROID_VIEW_MODEL_KEY = + "androidx.lifecycle.ViewModelProvider.DefaultKey" + } +} \ No newline at end of file diff --git a/projects/compose/koin-compose/build.gradle.kts b/projects/compose/koin-compose/build.gradle.kts index 89e28c795..ac0ac584b 100644 --- a/projects/compose/koin-compose/build.gradle.kts +++ b/projects/compose/koin-compose/build.gradle.kts @@ -5,6 +5,9 @@ plugins { alias(libs.plugins.compose) } +val koinComposeVersion: String by project +version = koinComposeVersion + kotlin { jvm { withJava() diff --git a/projects/core/koin-core-coroutines/src/commonMain/kotlin/org/koin/core/KoinApplicationLazyExt.kt b/projects/core/koin-core-coroutines/src/commonMain/kotlin/org/koin/core/KoinApplicationLazyExt.kt index c5eaa748a..857b97083 100644 --- a/projects/core/koin-core-coroutines/src/commonMain/kotlin/org/koin/core/KoinApplicationLazyExt.kt +++ b/projects/core/koin-core-coroutines/src/commonMain/kotlin/org/koin/core/KoinApplicationLazyExt.kt @@ -17,6 +17,7 @@ package org.koin.core +import kotlinx.coroutines.CoroutineDispatcher import org.koin.core.annotation.KoinExperimentalAPI import org.koin.core.annotation.KoinInternalApi import org.koin.core.extension.coroutinesEngine @@ -35,10 +36,10 @@ import org.koin.core.module.Module * run coroutinesEngine() to setup if needed */ @KoinExperimentalAPI -fun KoinApplication.lazyModules(vararg list: Lazy) { - coroutinesEngine() +fun KoinApplication.lazyModules(vararg moduleList: Lazy,dispatcher: CoroutineDispatcher? = null) { + coroutinesEngine(dispatcher) koin.coroutinesEngine.launchStartJob { - modules(list.map { it.value }) + modules(moduleList.map { it.value }) } } @@ -51,9 +52,9 @@ fun KoinApplication.lazyModules(vararg list: Lazy) { * run coroutinesEngine() to setup if needed */ @KoinExperimentalAPI -fun KoinApplication.lazyModules(list: List>) { - coroutinesEngine() +fun KoinApplication.lazyModules(moduleList: List>,dispatcher: CoroutineDispatcher? = null) { + coroutinesEngine(dispatcher) koin.coroutinesEngine.launchStartJob { - modules(list.map { it.value }) + modules(moduleList.map { it.value }) } } diff --git a/projects/core/koin-core-coroutines/src/commonMain/kotlin/org/koin/core/coroutine/KoinCoroutinesEngine.kt b/projects/core/koin-core-coroutines/src/commonMain/kotlin/org/koin/core/coroutine/KoinCoroutinesEngine.kt index 8ecfcd3b0..3dd236010 100644 --- a/projects/core/koin-core-coroutines/src/commonMain/kotlin/org/koin/core/coroutine/KoinCoroutinesEngine.kt +++ b/projects/core/koin-core-coroutines/src/commonMain/kotlin/org/koin/core/coroutine/KoinCoroutinesEngine.kt @@ -20,6 +20,7 @@ import org.koin.core.Koin import org.koin.core.annotation.KoinExperimentalAPI import org.koin.core.annotation.KoinInternalApi import org.koin.core.extension.KoinExtension +import org.koin.core.logger.Logger import org.koin.mp.KoinPlatformCoroutinesTools import kotlin.coroutines.CoroutineContext @@ -28,35 +29,43 @@ import kotlin.coroutines.CoroutineContext * * Help handle coroutines jobs for different purposes * - * @author Arnaud Giulani + * @author Arnaud Giuliani */ @KoinExperimentalAPI @KoinInternalApi -class KoinCoroutinesEngine : CoroutineScope, KoinExtension { - private val dispatcher: CoroutineDispatcher = KoinPlatformCoroutinesTools.defaultCoroutineDispatcher() +class KoinCoroutinesEngine(coroutineDispatcher: CoroutineDispatcher? = null) : CoroutineScope, KoinExtension { + private val dispatcher: CoroutineDispatcher = coroutineDispatcher ?: KoinPlatformCoroutinesTools.defaultCoroutineDispatcher() private val supervisorJob = SupervisorJob() override val coroutineContext: CoroutineContext = supervisorJob + dispatcher internal val startJobs = arrayListOf>() - override lateinit var koin: Koin + private var _koin: Koin? = null + private fun getKoin() : Koin = _koin ?: error("No Koin instance is registered for plugin $this") + private fun getLogger() : Logger = getKoin().logger + + override fun onRegister(koin: Koin) { + _koin = koin + koin.logger.debug("$TAG - init ($dispatcher)") + } fun launchStartJob(block: suspend CoroutineScope.() -> T) { startJobs.add(async { block() }) } suspend fun awaitAllStartJobs() { - koin.logger.debug("await All Start Jobs ...") + getLogger().debug("$TAG - await All Start Jobs ...") startJobs.map { it.await() } startJobs.clear() } override fun onClose() { - koin.logger.debug("onClose $this") + getLogger().debug("$TAG - onClose $this") cancel("KoinCoroutinesEngine shutdown") } companion object { + const val TAG = "[CoroutinesEngine]" const val EXTENSION_NAME = "coroutine-engine" } } diff --git a/projects/core/koin-core-coroutines/src/commonMain/kotlin/org/koin/core/extension/KoinCoroutinesExtension.kt b/projects/core/koin-core-coroutines/src/commonMain/kotlin/org/koin/core/extension/KoinCoroutinesExtension.kt index c141e39c9..9c14b5484 100644 --- a/projects/core/koin-core-coroutines/src/commonMain/kotlin/org/koin/core/extension/KoinCoroutinesExtension.kt +++ b/projects/core/koin-core-coroutines/src/commonMain/kotlin/org/koin/core/extension/KoinCoroutinesExtension.kt @@ -15,6 +15,7 @@ */ package org.koin.core.extension +import kotlinx.coroutines.CoroutineDispatcher import org.koin.core.Koin import org.koin.core.KoinApplication import org.koin.core.annotation.KoinExperimentalAPI @@ -31,10 +32,10 @@ import org.koin.core.coroutine.KoinCoroutinesEngine.Companion.EXTENSION_NAME */ @OptIn(KoinInternalApi::class) @KoinExperimentalAPI -fun KoinApplication.coroutinesEngine() { +fun KoinApplication.coroutinesEngine(dispatcher : CoroutineDispatcher? = null) { with(koin.extensionManager) { if (getExtensionOrNull(EXTENSION_NAME) == null) { - registerExtension(EXTENSION_NAME, KoinCoroutinesEngine()) + registerExtension(EXTENSION_NAME, KoinCoroutinesEngine(dispatcher)) } } } diff --git a/projects/core/koin-core-coroutines/src/jvmMain/kotlin/org/koin/mp/KoinPlatformCoroutinesTools.kt b/projects/core/koin-core-coroutines/src/jvmMain/kotlin/org/koin/mp/KoinPlatformCoroutinesTools.kt index 88e75296b..f1740bf54 100644 --- a/projects/core/koin-core-coroutines/src/jvmMain/kotlin/org/koin/mp/KoinPlatformCoroutinesTools.kt +++ b/projects/core/koin-core-coroutines/src/jvmMain/kotlin/org/koin/mp/KoinPlatformCoroutinesTools.kt @@ -21,5 +21,5 @@ import org.koin.core.annotation.KoinExperimentalAPI @KoinExperimentalAPI actual object KoinPlatformCoroutinesTools { - actual fun defaultCoroutineDispatcher(): CoroutineDispatcher = Dispatchers.IO + actual fun defaultCoroutineDispatcher(): CoroutineDispatcher = Dispatchers.Default } diff --git a/projects/core/koin-core-coroutines/src/jvmTest/kotlin/org/koin/test/LazyModuleTest.kt b/projects/core/koin-core-coroutines/src/jvmTest/kotlin/org/koin/test/LazyModuleTest.kt index 0ac574fac..3f655b0f2 100644 --- a/projects/core/koin-core-coroutines/src/jvmTest/kotlin/org/koin/test/LazyModuleTest.kt +++ b/projects/core/koin-core-coroutines/src/jvmTest/kotlin/org/koin/test/LazyModuleTest.kt @@ -1,5 +1,6 @@ package org.koin.test +import kotlinx.coroutines.Dispatchers import org.koin.core.lazyModules import org.koin.core.logger.Level import org.koin.core.module.dsl.bind @@ -39,6 +40,28 @@ class LazyModuleTest { assertNotNull(koin.getOrNull()) } + + @Test + fun test_dispatchers() { + var resolved: Boolean? = null + val m2 = lazyModule { + resolved = true + singleOf(::ClassB) + } + val m1 = lazyModule { + includes(m2) + singleOf(::ClassA) { bind() } + } + assertTrue(resolved == null, "resolved should be null: $resolved") + + val koin = koinApplication { + printLogger(Level.DEBUG) + lazyModules(m1, dispatcher = Dispatchers.IO) + }.koin + koin.waitAllStartJobs() + + assertNotNull(koin.getOrNull()) + } @Test fun test_plus() { diff --git a/projects/core/koin-core/build.gradle.kts b/projects/core/koin-core/build.gradle.kts index 4baf6e50a..c87aa0594 100644 --- a/projects/core/koin-core/build.gradle.kts +++ b/projects/core/koin-core/build.gradle.kts @@ -34,6 +34,7 @@ kotlin { sourceSets { commonMain.dependencies { implementation(libs.extras.stately) + implementation(libs.extras.stately.collections) } commonTest.dependencies { implementation(libs.kotlin.test) diff --git a/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/extension/ExtensionManager.kt b/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/extension/ExtensionManager.kt index 9940eb1c6..e625952bd 100644 --- a/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/extension/ExtensionManager.kt +++ b/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/extension/ExtensionManager.kt @@ -37,7 +37,7 @@ class ExtensionManager(internal val _koin: Koin) { fun registerExtension(id: String, extension: T) { extensions[id] = extension - extension.koin = _koin + extension.onRegister(_koin) } fun close() { diff --git a/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/extension/KoinExtension.kt b/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/extension/KoinExtension.kt index dfb853137..a2e5dc923 100644 --- a/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/extension/KoinExtension.kt +++ b/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/extension/KoinExtension.kt @@ -25,9 +25,9 @@ import org.koin.core.Koin interface KoinExtension { /** - * Current Koin instance + * register from Koin instance */ - var koin: Koin + fun onRegister(koin : Koin) /** * Called when closing Koin diff --git a/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/registry/ScopeRegistry.kt b/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/registry/ScopeRegistry.kt index 7e91adb69..95034e020 100644 --- a/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/registry/ScopeRegistry.kt +++ b/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/registry/ScopeRegistry.kt @@ -89,7 +89,7 @@ class ScopeRegistry(private val _koin: Koin) { } private fun closeAllScopes() { - _scopes.values.forEach { scope -> + _scopes.values.toList().forEach { scope -> scope.close() } } diff --git a/projects/core/koin-core/src/jsMain/kotlin/org/koin/mp/PlatformTools.kt b/projects/core/koin-core/src/jsMain/kotlin/org/koin/mp/PlatformTools.kt index 3612f0a69..ace661ba3 100644 --- a/projects/core/koin-core/src/jsMain/kotlin/org/koin/mp/PlatformTools.kt +++ b/projects/core/koin-core/src/jsMain/kotlin/org/koin/mp/PlatformTools.kt @@ -16,6 +16,7 @@ package org.koin.mp +import co.touchlab.stately.collections.ConcurrentMutableMap import co.touchlab.stately.concurrency.ThreadLocalRef import org.koin.core.context.GlobalContext import org.koin.core.context.KoinContext @@ -51,7 +52,7 @@ actual object KoinPlatformTools { actual fun defaultLogger(level: Level): Logger = PrintLogger(level) actual fun defaultContext(): KoinContext = GlobalContext actual fun synchronized(lock: Lockable, block: () -> R) = block() - actual fun safeHashMap(): MutableMap = HashMap() + actual fun safeHashMap(): MutableMap = ConcurrentMutableMap() } actual typealias Lockable = Any diff --git a/projects/core/koin-core/src/nativeMain/kotlin/org/koin/mp/KoinPlatformTools.kt b/projects/core/koin-core/src/nativeMain/kotlin/org/koin/mp/KoinPlatformTools.kt index 4b92b9a67..e1ba05cf0 100644 --- a/projects/core/koin-core/src/nativeMain/kotlin/org/koin/mp/KoinPlatformTools.kt +++ b/projects/core/koin-core/src/nativeMain/kotlin/org/koin/mp/KoinPlatformTools.kt @@ -1,5 +1,6 @@ package org.koin.mp +import co.touchlab.stately.collections.ConcurrentMutableMap import co.touchlab.stately.concurrency.Lock import co.touchlab.stately.concurrency.withLock import co.touchlab.stately.concurrency.ThreadLocalRef @@ -28,7 +29,7 @@ actual object KoinPlatformTools { actual fun synchronized(lock: Lockable, block: () -> R): R = lock.lock.withLock { block() } - actual fun safeHashMap(): MutableMap = HashMap() + actual fun safeHashMap(): MutableMap = ConcurrentMutableMap() } actual open class Lockable { diff --git a/projects/core/koin-test-junit4/build.gradle.kts b/projects/core/koin-test-junit4/build.gradle.kts index ca5471d44..517a9c50b 100644 --- a/projects/core/koin-test-junit4/build.gradle.kts +++ b/projects/core/koin-test-junit4/build.gradle.kts @@ -7,7 +7,7 @@ plugins { dependencies { api(project(":core:koin-test")) implementation(libs.test.junit) - implementation(libs.kotlin.test) + testImplementation(libs.kotlin.test) testImplementation(libs.test.mockito) } diff --git a/projects/core/koin-test-junit4/src/test/kotlin/org/koin/test/CheckModulesTest.kt b/projects/core/koin-test-junit4/src/test/kotlin/org/koin/test/CheckModulesTest.kt index 137d626ac..cc77360e4 100644 --- a/projects/core/koin-test-junit4/src/test/kotlin/org/koin/test/CheckModulesTest.kt +++ b/projects/core/koin-test-junit4/src/test/kotlin/org/koin/test/CheckModulesTest.kt @@ -1,15 +1,15 @@ package org.koin.test import org.junit.Assert.fail +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.koin.core.logger.Level +import org.koin.core.module.dsl.createdAtStart +import org.koin.core.module.dsl.withOptions import org.koin.core.parameter.parametersOf import org.koin.core.qualifier.named -import org.koin.dsl.bind -import org.koin.dsl.binds -import org.koin.dsl.koinApplication -import org.koin.dsl.module +import org.koin.dsl.* import org.koin.test.check.checkKoinModules import org.koin.test.check.checkModules import org.koin.test.mock.MockProviderRule @@ -17,6 +17,8 @@ import org.mockito.Mockito import java.util.* import kotlin.test.assertEquals import kotlin.test.assertFailsWith +import kotlin.test.assertNotNull +import kotlin.test.assertTrue class CheckModulesTest { @@ -87,6 +89,69 @@ class CheckModulesTest { } } + @Test + fun `check a module - created at start`() { + val modules = module { + single { Simple.ComponentB(get()) } withOptions { createdAtStart() } + } + + koinApplication(createEagerInstances = false) { + modules(modules) + checkModules { + withInstance() + } + } + } + + @Test + fun `check module - dependency and param in one class`() { + val modules = module { + factory { p -> Simple.MyString(p.get()) } + } + + koinApplication { + modules(modules) + checkModules { + withParameter { "test" } + } + } + } + + @Test + fun `check module - dependency and param in one class - 2`() { + val modules = module { + factory { Simple.ComponentA() } + factory { p -> Simple.MyComplexString2(get(), p.get()) } + } + + koinApplication { + printLogger(Level.DEBUG) + modules(modules) + checkModules { +// withInstance() + withParameter { "test" } + } + } + } + + // Not working + @Test + @Ignore + fun `check module - dependency and param in one class - 3`() { + val modules = module { + factory { p -> Simple.MyComplexBool(get(), p.get()) } + } + + koinApplication { + printLogger(Level.DEBUG) + modules(modules) + checkModules { + withInstance() + withParameter { true } + } + } + } + @Test fun `check a scoped module`() { koinApplication { @@ -306,8 +371,8 @@ class CheckModulesTest { printLogger(Level.DEBUG) modules( module { - single { (s: String) -> Simple.MyString(s) } - single(UpperCase) { (s: String) -> Simple.MyString(s.uppercase(Locale.getDefault())) } + single { p -> Simple.MyString(p.get()) } + single(UpperCase) { p -> Simple.MyString(p.get().uppercase(Locale.getDefault())) } }, ) }.checkModules { @@ -589,9 +654,6 @@ class CheckModulesTest { }.checkModules() } - assertEquals( - expected = "instance of class java.lang.String (Kotlin reflection is not available) is not inheritable from int (Kotlin reflection is not available)", - actual = exception.message, - ) + assertNotNull(exception.message) } } diff --git a/projects/core/koin-test-junit4/src/test/kotlin/org/koin/test/Components.kt b/projects/core/koin-test-junit4/src/test/kotlin/org/koin/test/Components.kt index 4ac4843b4..259713684 100644 --- a/projects/core/koin-test-junit4/src/test/kotlin/org/koin/test/Components.kt +++ b/projects/core/koin-test-junit4/src/test/kotlin/org/koin/test/Components.kt @@ -11,6 +11,9 @@ class Simple { class ComponentD() class ComponentE(val d: ComponentD) class MyString(val s: String) + class MyComplexString(val s: String, val a : ComponentA) + class MyComplexString2(val a : ComponentA, val s: String) + class MyComplexBool(val a : ComponentA, val b : Boolean) class UUIDComponent { fun getUUID() = UUID.randomUUID().toString() diff --git a/projects/core/koin-test/build.gradle.kts b/projects/core/koin-test/build.gradle.kts index b92e7008b..1e6df8230 100644 --- a/projects/core/koin-test/build.gradle.kts +++ b/projects/core/koin-test/build.gradle.kts @@ -34,7 +34,9 @@ kotlin { sourceSets { commonMain.dependencies { api(project(":core:koin-core")) - implementation(libs.kotlin.test) + } + jvmMain.dependencies { + implementation(kotlin("reflect")) } commonTest.dependencies { implementation(libs.kotlin.test) diff --git a/projects/core/koin-test/src/commonMain/kotlin/org/koin/test/check/CheckModules.kt b/projects/core/koin-test/src/commonMain/kotlin/org/koin/test/check/CheckModules.kt index 44a6e4cd1..96eb7c472 100644 --- a/projects/core/koin-test/src/commonMain/kotlin/org/koin/test/check/CheckModules.kt +++ b/projects/core/koin-test/src/commonMain/kotlin/org/koin/test/check/CheckModules.kt @@ -35,6 +35,8 @@ import org.koin.mp.KoinPlatformTools import org.koin.test.mock.MockProvider import org.koin.test.parameter.MockParameter +//TODO TO BE DEPRECATED in 3.6 + /** * Check all definition's dependencies - start all modules and check if definitions can run */ @@ -152,6 +154,7 @@ private fun Koin.checkDefinition( ]?.invoke( definition.qualifier, ) ?: MockParameter(scope, allParameters.defaultValues) + logger.info("[Check] definition: $definition") scope.get(definition.primaryType, definition.qualifier) { parameters } diff --git a/projects/core/koin-test/src/commonMain/kotlin/org/koin/test/check/CheckModulesDSL.kt b/projects/core/koin-test/src/commonMain/kotlin/org/koin/test/check/CheckModulesDSL.kt index f154ac1af..f41ab1310 100644 --- a/projects/core/koin-test/src/commonMain/kotlin/org/koin/test/check/CheckModulesDSL.kt +++ b/projects/core/koin-test/src/commonMain/kotlin/org/koin/test/check/CheckModulesDSL.kt @@ -24,6 +24,8 @@ import org.koin.mp.KoinPlatformTools import org.koin.test.mock.MockProvider import kotlin.reflect.KClass +//TODO TO BE DEPRECATED in 3.6 + data class CheckedComponent(val qualifier: Qualifier? = null, val type: KClass<*>) class ParametersBinding(val koin: Koin) { diff --git a/projects/core/koin-test/src/commonTest/kotlin/org/koin/test/Components.kt b/projects/core/koin-test/src/commonTest/kotlin/org/koin/test/Components.kt index 9765356ed..d1a8fe736 100644 --- a/projects/core/koin-test/src/commonTest/kotlin/org/koin/test/Components.kt +++ b/projects/core/koin-test/src/commonTest/kotlin/org/koin/test/Components.kt @@ -18,6 +18,10 @@ class Simple { class CycleAB(val b: CycleBA) class CycleBA(val a: CycleAB) + + class MyComplexBool(val a : ComponentA, val b : Boolean) + + fun buildB(a: ComponentA) : MyComponentB = ComponentB(a) } object UpperCase : Qualifier { diff --git a/projects/core/koin-test/src/jvmTest/kotlin/VerifyModulesTest.kt b/projects/core/koin-test/src/jvmTest/kotlin/VerifyModulesTest.kt index d1acfa89c..74f2b6d7b 100644 --- a/projects/core/koin-test/src/jvmTest/kotlin/VerifyModulesTest.kt +++ b/projects/core/koin-test/src/jvmTest/kotlin/VerifyModulesTest.kt @@ -1,8 +1,12 @@ +import org.koin.core.logger.Level +import org.koin.core.module.dsl.factoryOf import org.koin.core.module.dsl.singleOf import org.koin.core.qualifier.named import org.koin.dsl.bind +import org.koin.dsl.koinApplication import org.koin.dsl.module import org.koin.test.Simple +import org.koin.test.check.checkModules import org.koin.test.verify.CircularInjectionException import org.koin.test.verify.MissingKoinDefinitionException import org.koin.test.verify.Verify @@ -162,4 +166,22 @@ class VerifyModulesTest { System.err.println("$e") } } + + @Test + fun `verify dependency and param in one class - 3`() { + val modules = module { + factory { p -> Simple.MyComplexBool(get(), p.get()) } + } + + modules.verify(extraTypes = listOf(Simple.ComponentA::class, Boolean::class)) + } + + @Test + fun `verify function builder`() { + val modules = module { + factoryOf(Simple::buildB) + } + + modules.verify(extraTypes = listOf(Simple.ComponentA::class)) + } } diff --git a/projects/gradle.properties b/projects/gradle.properties index 94ebe34d5..2a20a43d7 100644 --- a/projects/gradle.properties +++ b/projects/gradle.properties @@ -6,8 +6,11 @@ org.gradle.parallel=true #Kotlin kotlin.code.style=official + #Koin -koinVersion=3.6.0-alpha1 +koinVersion=3.6.0-Beta1 +koinComposeVersion=1.2.0-Beta1 + #Compose composeCompiler=1.5.7 #Android diff --git a/projects/gradle/libs.versions.toml b/projects/gradle/libs.versions.toml index 00036c9b5..add383832 100644 --- a/projects/gradle/libs.versions.toml +++ b/projects/gradle/libs.versions.toml @@ -23,7 +23,7 @@ composeJB = "1.5.11" composeJetpackRuntime = "1.5.4" composeJetpackViewmodel = "2.6.2" # Test -stately = "2.0.5" +stately = "2.0.6" junit = "4.13.2" jupiter = "5.9.3" mockito = "4.7.0" @@ -36,6 +36,8 @@ slf4j = "1.7.36" # Core kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } extras-stately = { module = "co.touchlab:stately-concurrency", version.ref = "stately" } +extras-stately-collections = { module = "co.touchlab:stately-concurrent-collections", version.ref = "stately" } + # Test kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } test-junit = { module = "junit:junit", version.ref = "junit" } diff --git a/projects/test-macos.sh b/projects/test-macos.sh new file mode 100755 index 000000000..e5d676bb8 --- /dev/null +++ b/projects/test-macos.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +./gradlew cleanTest :core:koin-core:macosArm64Test --parallel --no-build-cache