Migrating from Kotlin Android Extensions to Kace: A Seamless Migration Guide
This article explains why the Kotlin Android Extensions plugin is deprecated, outlines migration strategies for legacy code, introduces the open‑source Kace framework as a seamless replacement, and provides detailed setup, code generation, performance optimizations, and incremental compilation techniques for Android projects.
Introduction
The Kotlin Android Extensions (KAE) plugin was announced deprecated in 2020 and will be removed in Kotlin 1.8, so projects upgrading to Kotlin 1.8 must eliminate KAE.
Migration Options
The official recommendation is to migrate old projects to ViewBinding or Jetpack Compose. For large legacy codebases that heavily use KAE, manual migration is costly and error‑prone.
Legacy Code Migration Strategies
Three main approaches exist: manual modification (high cost, risk of bugs), extracting KAE into a separate module (high maintenance due to deep compiler‑plugin dependencies), and the third approach—using Kace.
What Is Kace?
Kace (kotlin-android-compatible-extensions) is an open‑source framework that helps migrate from KAE seamlessly. Repository: https://github.com/kanyun-inc/Kace .
Key advantages:
Easy integration without manual code changes.
Behavior identical to KAE (viewId caching and automatic cleanup).
Unified migration that simplifies regression testing.
Low maintenance by generating source code.
Quick Migration Steps
1. Add the plugin to the classpath
// Method 1 – traditional way in root build.gradle.kts
buildscript {
repositories { mavenCentral() }
dependencies { classpath("com.kanyun.kace:kace-gradle-plugin:1.0.0") }
}
// Method 2 – new way in settings.gradle.kts
pluginManagement {
repositories { mavenCentral() }
plugins {
id("com.kanyun.kace") version "1.0.0" apply false
}
}2. Apply the plugin
Remove the kotlin-android-extensions plugin and add:
plugins {
id("com.kanyun.kace")
id("kotlin-parcelize") // optional if @Parcelize is used
}3. (Optional) Configure the plugin
By default Kace parses every layout. You can whitelist or blacklist specific layouts:
kace {
whiteList = listOf() // only layouts in this list will be processed
blackList = listOf("activity_main.xml") // these layouts will be ignored
}After these steps the migration is complete.
Supported Types
Kace currently supports the four most common view types (Activity, Fragment, View, and synthetic extensions). Less‑used types such as android.app.Fragment or LayoutContainer are not yet supported.
Version Compatibility
Kotlin
AGP
Gradle
Minimum supported version
1.7.0
4.2.0
6.7.1
Kace targets Kotlin 1.8, so the minimum Kotlin version is relatively high.
Prerequisite Knowledge: Compiler Plugins
What Is a Compiler Plugin?
Kotlin compiler plugins hook into the compilation process, allowing custom logic to modify the generated bytecode or IR. They can be Gradle plugins, compiler plugins, or IDE plugins.
How KAE Works
KAE consists of several components: AndroidExtensionsSubpluginIndicator (entry point), AndroidSubplugin (parameter configuration), AndroidCommandLineProcessor (receives parameters), and AndroidComponentRegistrar (registers extensions). It converts view IDs into cached findViewByIdCached calls and clears the cache on activity destruction.
Kace Implementation
First Attempt
Generated synthetic properties for each layout, e.g.:
val AndroidExtensions.button1 get() = findViewByIdCached<Button>(R.id.button1)
class MainActivity : AppCompatActivity(), AndroidExtensions {
private val androidExtensionImpl by lazy { AndroidExtensionsImpl() }
override fun
findViewByIdCached(owner: AndroidExtensionsBase, id: Int): T {
return androidExtensionImpl.findViewByIdCached(id)
}
}This required a lot of boilerplate per activity.
Second Attempt
Used delegation to reduce boilerplate:
private inline val AndroidExtensions.button1 get() = findViewByIdCached<Button>(this, R.id.button1)
class MainActivity : AppCompatActivity(), AndroidExtensions by AndroidExtensionsImpl() {
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) }
}Still not fully seamless for hundreds of screens.
Third Attempt – Final Kace Design
Kace combines a Gradle plugin (kace-gradle-plugin) that parses XML layouts and a compiler plugin (kace-compiler) that injects the AndroidExtensions interface and its implementation into Activities/Fragments.
Key components:
KaceSyntheticResolveExtension – adds the interface.
KaceIrGenerationExtension – generates the implementation.
Generated code example:
class MainActivity : AppCompatActivity(), AndroidExtensions {
private val $$androidExtensionImpl by lazy { AndroidExtensionsImpl() }
override fun
findViewByIdCached(owner: AndroidExtensionsBase, id: Int): T {
return $$androidExtensionImpl.findViewByIdCached(id)
}
}Kace Performance Optimizations
Explicit Input/Output
The KaceGenerateTask declares its inputs (layout XML files) and outputs (generated source) using Gradle annotations, allowing up‑to‑date checks to skip unnecessary work.
Parallel Task Execution
Each layout file is processed independently via the Gradle Worker API, turning the task into a set of parallel actions. In a module with 500 layouts, full compilation dropped from ~4 s to ~2 s.
Incremental Compilation
By marking the layout collection with @Incremental and handling InputChanges , only modified or added layouts are re‑processed, reducing incremental compile time to around 8 ms for a single layout change.
Conclusion
The article demonstrates how to adopt Kace for a painless migration from KAE, explains its internal architecture, and shows practical performance tricks such as up‑to‑date checks, parallel execution, and incremental compilation.
Open‑Source Repository
https://github.com/kanyun-inc/Kace
Yuanfudao Tech
Official Yuanfudao technology account, using tech to empower education development.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.