· 5 min read Posted by Jigar Brahmbhatt

Kotlin 1.9.20: Streamlining Source Sets in Multiplatform Project

Explore how Kotlin 1.9.20's new features significantly enhance the experience of setting up target source sets
Photo by JESHOOTS.COM on Unsplash (https://unsplash.com/photos/person-holding-yellow-plastic-spray-bottle-__ZMnefoI3k)
Credit: Photo by JESHOOTS.COM on Unsplash (https://unsplash.com/photos/person-holding-yellow-plastic-spray-bottle-__ZMnefoI3k)

The release of Kotlin 1.9.20 marked a significant milestone for Kotlin Multiplatform enthusiasts, as this update moved Kotlin Multiplatform to Stable. Kotlin 1.9.20 introduces several notable features and enhancements. Among these updates, the new source set hierarchy template by the Kotlin Gradle Plugin stands out to me for its ability to simplify configuration complexities. It was launched as experimental in Kotlin 1.8.20 and has been made default in Kotlin 1.9.20.

Simplified iOS Target Configuration

In the common Kotlin Multiplatform project set-up, you would have seen specific source set setup for two or three iOS targets like for X64, Arm64.

Instead of the common and confusing ‘ios()’ target, one must always declare specific targets like ‘iosArm64()’ as required. Once one of the iOS targets is declared, the default template would automatically create iosMain source set. So no more val iosMain by creating boilerplate required.

Before 1.9.20 After 1.9.20
kotlin {
    ios()
    iosSimulatorArm64()

    sourceSets {
        val commonMain by getting

        val iosMain by creating {
            dependsOn(commonMain)
        }

        val iosX64Main by getting {
            dependsOn(iosMain)
        }

        val iosArm64Main by getting {
            dependsOn(iosMain)
        }

        val iosSimulatorArm64Main by getting {
            dependsOn(iosMain)
        }
    }
}
kotlin {
    iosX64()
    iosArm64()
    iosSimulatorArm64()

    // The iosMain source set is now created automatically
    // You can directly use the reference now
    iosMain.dependencies {
        implementation("...")
    }
}

If you’ve an old KMP project and still have reference of darwinMain, you can simply rename it to appleMain now as that encapsulates all other apple targets.

Checkout the full hierarchy template!

Streamlined Custom Source Sets

Managing custom source sets in Kotlin Multiplatform projects is now more straightforward with Kotlin 1.9.20. The update simplifies the process of setting up dependencies between source sets, reducing the need for extensive boilerplate code and enhancing overall readability.

For example, let’s assume that you already have kotlin/JS support with jsMain/jsTest source sets. Now, you want to add kotlinWasm support. Since wasmJs and kotlinJs can share a common source, you want to introduce a new jsAndWasmJsMain parent source set for common code sharing between them. Instead of using by creating, you can declare new custom source set inside applyDefaultHierarchyTemplate {}.

Before 1.9.20 After 1.9.20
kotlin {
    js()
    wasmJs()

    sourceSets {
        val commonMain by getting
        val commonTest by getting

        val jsAndWasmJsMain by creating {
            dependsOn(commonMain)
            getByName("jsMain").dependsOn(this)
            getByName("wasmMain").dependsOn(this)
        }

        val jsAndWasmJsTest by creating {
            dependsOn(commonTest)
            getByName("jsTest").dependsOn(this)
            getByName("wasmTest").dependsOn(this)
        }
    }
}
kotlin {
    js()
    wasmJs()

    // This is an experimental API, so opt-in is required
    @OptIn(ExperimentalKotlinGradlePluginApi::class)
    applyDefaultHierarchyTemplate {
        // create a new group that
        // depends on `common`
        common {
            // Define group name without
            // `Main` as suffix
            group("jsAndWasmJs") {
                // Provide which targets would
                // be part of this group
                withJs()
                withWasm()
            }
        }
    }

    // Now you will already have `jsAndWasmJsMain`
}

Manually apply the template

Note that if you have explict by creating usage for custom source set then defauly template hierarchy would not apply automatically and you would see warning about the same.

w: The Default Kotlin Hierarchy Template was not applied to 'project ':XXX'':
Explicit .dependsOn() edges were configured for the following source sets:
[XXX]

In order to get the default hierachy, you would have to call applyDefaultHierarchyTemplate() manually once you declare all the targets.

Also, checkout Opting Out of Default Templates

Enhanced Code Completion Support

The updated source set hierarchy template enhances code completion support in the IDE. Developers can now utilize predefined source sets directly, eliminating the need for repetitive declarations and enhancing overall development efficiency.

So even in simple cases, you can still clean up your source set declarations, and make them more readable.

Default before 1.9.20 With 1.9.20
kotlin {
    // ...
    sourceSets {
        val commonMain by getting {
            dependencies {
                // ...
            }
        }
        val androidMain by getting {
            dependencies {
                // ...
            }
        }
        val iosMain by creating {
            dependencies {
                // ...
            }
        }
    }
}
kotlin {
    // ...
    sourceSets {
        commonMain.dependencies {
            // ...
        }
        androidMain.dependencies {
            // ..
        }
        iosMain.dependencies {
            // ...
        }
    }
}

Android instrumented test changes

With introduction of the new Android source set layout (now default starting Kotlin 1.9.0), the androidInstrumentedTest source set doesn’t automatically depend on commonTest.

In order to have that dependency set-up, with 1.9.0, you would need to explicitly set the dependsOn relationship between them, and with 1.9.20, you now just need to set the correct sourceSetTree property inside android target.

With 1.9.0 With 1.9.20
kotlin {
    // ...
    sourceSets {
        val commonTest by getting
        val androidInstrumentedTest by getting {
            dependsOn(commonTest)
        }
    }
}
kotlin {
    androidTarget {
        // This is an experimental API, so opt-in is required
        @OptIn(ExperimentalKotlinGradlePluginApi::class)
        instrumentedTestVariant {
            // This makes instrumented tests depend on commonTest source
            sourceSetTree.set(KotlinSourceSetTree.test)
        }
    }
}

Opting Out of Default Templates

For projects with existing complex source set hierarchies, Kotlin 1.9.20 offers the flexibility to opt-out of the default template. By setting kotlin.mpp.applyDefaultHierarchyTemplate=false in the gradle.properties, developers can retain their current configurations.

Conclusion

As you transition to Kotlin 1.9.20, consider how these improvements can optimize your Gradle configurations and enhance your multiplatform development workflow.

We recently updated most of our OSS projects to use Kotlin 1.9.22, leveraging the benefits of these enhancements. We hope you find the transition as beneficial as we have.