
Cross-module service provisioning framework enables service implementation retrieval through annotations, supporting multi-module projects and Compose Multiplatform environments, with automatic dependency resolution and initialization.
Cross-Module Service Provisioning Framework with KMP Support
Across all modules:
// build.gradle.kts
plugins {
id("com.google.devtools.ksp")
id("io.github.985892345.KtProvider") version "x.y.z"
}
kotlin {
sourceSets {
commonMain.dependencies {
// The provider-manager dependency can be optionally added.
// Additionally, you have the option to implement your own provider-manager.
implementation(ktProvider.manager)
}
}
}
dependencies {
// Please refer to the official documentation for KSP configurations:
// https://kotlinlang.org/docs/ksp-multiplatform.html
add("kspCommonMainMetadata", ktProvider.ksp)
add("kspAndroid", ktProvider.ksp)
add("kspDesktop", ktProvider.ksp) // dependent on JVM source set configuration
add("kspIosX64", ktProvider.ksp)
add("kspIosArm64", ktProvider.ksp)
add("kspIosSimulatorArm64", ktProvider.ksp)
// ...
// The provider-api dependency is already included with the Gradle plugin.
}// build.gradle.kts
plugins {
id("com.google.devtools.ksp")
id("io.github.985892345.KtProvider") version "x.y.z"
}
dependencies {
// The provider-manager dependency can be optionally added.
// Additionally, you have the option to implement your own provider-manager.
implementation(ktProvider.manager)
// ksp
ksp(ktProvider.ksp)
}Kotlin/Jvm: It is recommended to perform initialization in the main function.
fun main() {
// Invoking the generated XXXKtProviderInitializer (Module Name + KtProviderInitializer) via KSP
XXXKtProviderInitializer.tryInitKtProvider()
}Android: It is recommended to perform initialization in the Application#onCreate method.
class App : Application() {
override fun onCreate() {
super.onCreate()
XXXKtProviderInitializer.tryInitKtProvider()
}
}iOS: I'm not proficient in iOS, so this may not be the most optimal approach.
Swift: App#init
@main
struct iOSApp: App {
init() {
XXXKtProviderInitializer.shared.tryInitKtProvider()
}
}Objective-C: application:didFinishLaunchingWithOptions:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[XXXKtProviderInitializer.shared tryInitKtProvider];
}Add interface
interface ITestService {
fun get(): String
}Implement interface
@ImplProvider(clazz = ITestService::class, name = "test")
class TestServiceImpl : ITestService {
override fun get(): String {
return "123"
}
}| Annotation | |
|---|---|
| ImplProvider | Obtain an instance, which becomes a singleton when the implementation class is an object. |
| KClassProvider | Retrieve the KClass of the implementation class (can be used for Class<out Activity> in Android). |
Use interface
val service = KtProvider.implOrNull(ITestService::class, "test")
println(service.get())Add provider-testing to the test source set. The main source set still needs
provider-manager as shown in Setup.
kotlin {
sourceSets {
commonTest.dependencies {
implementation(kotlin("test"))
implementation(ktProvider.testing)
}
}
}dependencies {
testImplementation(ktProvider.testing)
}Use the initializer generated for the module under test. withKtProviderTest creates an isolated
registry, loads the module and the KtProvider project-module dependencies recorded in its generated
initializer, redirects KtProvider lookups for the block, and restores the production resolver when
the block returns or throws.
import com.g985892345.provider.testing.withKtProviderTest
import kotlin.test.Test
import kotlin.test.assertEquals
private object FakeTestService : ITestService {
override fun get(): String = "fake"
}
class ServiceConsumerTest {
@Test
fun usesTheTestProvider() = withKtProviderTest(ModuleKtProviderInitializer) {
overrideImpl<ITestService>(name = "test") {
FakeTestService
}
assertEquals(
"fake",
KtProvider.impl(ITestService::class, "test").get(),
)
}
}Implementation-class routes can be replaced in the same way:
withKtProviderTest(ModuleKtProviderInitializer) {
overrideKClass<ITestService>(name = "test") {
FakeTestService::class
}
}The generated ModuleKtProviderInitializer is produced by the main KSP task. Running the test task
also runs the required main compilation and KSP generation. If the reference is unresolved immediately
after writing the first test, run Gradle sync or the test task once.
Each withKtProviderTest call owns a fresh registry, so overrides do not mutate production registrations
or leak into later sequential tests. The block is synchronous and resolver scopes must not overlap; do not
run multiple withKtProviderTest blocks concurrently in the same test process.
Similar to the following code:
// Implementation class of KtProviderInitializer
object ModuleKtProviderInitializer : KtProviderInitializer() {
override val router: KtProviderRouter = ModuleKtProviderRouter
override val otherModuleKtProvider: List<KtProviderInitializer> = listOf(
// Here, based on the module dependency relationship during Gradle compilation,
// the implementation class of KtProviderInitializer for the dependent modules is generated.
Module1KtProviderInitializer,
Module2KtProviderInitializer
)
}// Implementation class of KtProviderRouter
internal object ModuleKtProviderRouter : KtProviderRouter() {
override fun initRouter(delegate: IKtProviderDelegate) {
// By using KSP, find all annotations and then add the implementation classes.
delegate.addImplProvider(ITestService::class, "abc") { TestServiceImpl }
delegate.addImplProvider(ITestService2::class, "123") { TestServiceImpl2 }
}
}Copyright 2023 985892345
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Cross-Module Service Provisioning Framework with KMP Support
Across all modules:
// build.gradle.kts
plugins {
id("com.google.devtools.ksp")
id("io.github.985892345.KtProvider") version "x.y.z"
}
kotlin {
sourceSets {
commonMain.dependencies {
// The provider-manager dependency can be optionally added.
// Additionally, you have the option to implement your own provider-manager.
implementation(ktProvider.manager)
}
}
}
dependencies {
// Please refer to the official documentation for KSP configurations:
// https://kotlinlang.org/docs/ksp-multiplatform.html
add("kspCommonMainMetadata", ktProvider.ksp)
add("kspAndroid", ktProvider.ksp)
add("kspDesktop", ktProvider.ksp) // dependent on JVM source set configuration
add("kspIosX64", ktProvider.ksp)
add("kspIosArm64", ktProvider.ksp)
add("kspIosSimulatorArm64", ktProvider.ksp)
// ...
// The provider-api dependency is already included with the Gradle plugin.
}// build.gradle.kts
plugins {
id("com.google.devtools.ksp")
id("io.github.985892345.KtProvider") version "x.y.z"
}
dependencies {
// The provider-manager dependency can be optionally added.
// Additionally, you have the option to implement your own provider-manager.
implementation(ktProvider.manager)
// ksp
ksp(ktProvider.ksp)
}Kotlin/Jvm: It is recommended to perform initialization in the main function.
fun main() {
// Invoking the generated XXXKtProviderInitializer (Module Name + KtProviderInitializer) via KSP
XXXKtProviderInitializer.tryInitKtProvider()
}Android: It is recommended to perform initialization in the Application#onCreate method.
class App : Application() {
override fun onCreate() {
super.onCreate()
XXXKtProviderInitializer.tryInitKtProvider()
}
}iOS: I'm not proficient in iOS, so this may not be the most optimal approach.
Swift: App#init
@main
struct iOSApp: App {
init() {
XXXKtProviderInitializer.shared.tryInitKtProvider()
}
}Objective-C: application:didFinishLaunchingWithOptions:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[XXXKtProviderInitializer.shared tryInitKtProvider];
}Add interface
interface ITestService {
fun get(): String
}Implement interface
@ImplProvider(clazz = ITestService::class, name = "test")
class TestServiceImpl : ITestService {
override fun get(): String {
return "123"
}
}| Annotation | |
|---|---|
| ImplProvider | Obtain an instance, which becomes a singleton when the implementation class is an object. |
| KClassProvider | Retrieve the KClass of the implementation class (can be used for Class<out Activity> in Android). |
Use interface
val service = KtProvider.implOrNull(ITestService::class, "test")
println(service.get())Add provider-testing to the test source set. The main source set still needs
provider-manager as shown in Setup.
kotlin {
sourceSets {
commonTest.dependencies {
implementation(kotlin("test"))
implementation(ktProvider.testing)
}
}
}dependencies {
testImplementation(ktProvider.testing)
}Use the initializer generated for the module under test. withKtProviderTest creates an isolated
registry, loads the module and the KtProvider project-module dependencies recorded in its generated
initializer, redirects KtProvider lookups for the block, and restores the production resolver when
the block returns or throws.
import com.g985892345.provider.testing.withKtProviderTest
import kotlin.test.Test
import kotlin.test.assertEquals
private object FakeTestService : ITestService {
override fun get(): String = "fake"
}
class ServiceConsumerTest {
@Test
fun usesTheTestProvider() = withKtProviderTest(ModuleKtProviderInitializer) {
overrideImpl<ITestService>(name = "test") {
FakeTestService
}
assertEquals(
"fake",
KtProvider.impl(ITestService::class, "test").get(),
)
}
}Implementation-class routes can be replaced in the same way:
withKtProviderTest(ModuleKtProviderInitializer) {
overrideKClass<ITestService>(name = "test") {
FakeTestService::class
}
}The generated ModuleKtProviderInitializer is produced by the main KSP task. Running the test task
also runs the required main compilation and KSP generation. If the reference is unresolved immediately
after writing the first test, run Gradle sync or the test task once.
Each withKtProviderTest call owns a fresh registry, so overrides do not mutate production registrations
or leak into later sequential tests. The block is synchronous and resolver scopes must not overlap; do not
run multiple withKtProviderTest blocks concurrently in the same test process.
Similar to the following code:
// Implementation class of KtProviderInitializer
object ModuleKtProviderInitializer : KtProviderInitializer() {
override val router: KtProviderRouter = ModuleKtProviderRouter
override val otherModuleKtProvider: List<KtProviderInitializer> = listOf(
// Here, based on the module dependency relationship during Gradle compilation,
// the implementation class of KtProviderInitializer for the dependent modules is generated.
Module1KtProviderInitializer,
Module2KtProviderInitializer
)
}// Implementation class of KtProviderRouter
internal object ModuleKtProviderRouter : KtProviderRouter() {
override fun initRouter(delegate: IKtProviderDelegate) {
// By using KSP, find all annotations and then add the implementation classes.
delegate.addImplProvider(ITestService::class, "abc") { TestServiceImpl }
delegate.addImplProvider(ITestService2::class, "123") { TestServiceImpl2 }
}
}Copyright 2023 985892345
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.