SpatialFeature modules as separate Android library Gradle modules. Unlike other samples that define features inline, this is the only sample in the Spatial SDK Samples corpus that packages custom features as standalone libraries for reuse across multiple projects. You learn how to structure feature modules, define custom components, and integrate both pure Kotlin and native C++ code.SpatialFeature modules as separate Android library Gradle modulesSpatialFeature interface with custom components and systemsmaven-publish for distribution across projectsnativefeature module with native C++ code)FeatureDevSample directory in Android Studio. Build and deploy to your Meta Quest device. You see two animated entities: a basketball bobbing up and down using native C++ calculations, and a robot pulsing in scale using pure Kotlin calculations.:app (main application), :kotlinfeature (pure Kotlin feature library), and :nativefeature (Kotlin + JNI feature library).| File / Module | What it demonstrates | Key APIs / patterns |
|---|---|---|
settings.gradle.kts | Multi-module project structure | :app, :nativefeature, :kotlinfeature |
app/FeatureDevSampleActivity.kt | Feature registration and scene composition loading | registerFeatures(), glXFManager.inflateGLXF() |
app/Panels.kt | Compose UI with Horizon OS UI Set | SpatialTheme, darkSpatialColorScheme() |
kotlinfeature/PulsingFeature.kt | Pure Kotlin SpatialFeature implementation | componentsToRegister(), systemsToRegister() |
kotlinfeature/PulsingSystem.kt | ECS system with per-frame animation | Query.where { has(...) }.eval(), Scale(Float) |
kotlinfeature/components.xml | Component schema definition | FloatAttribute, ComponentSchema.xsd |
kotlinfeature/build.gradle.kts | Library module configuration | android.library, registrationsClassName, maven-publish |
nativefeature/NativeBobbingFeature.kt | JNI-integrated SpatialFeature | preRuntimeOnCreate(), loadLibrary() |
nativefeature/NativeBobbingSystem.kt | JNI-based animation system | external fun, delete(entity) cleanup |
nativefeature/native_bobbing.cpp | Native C++ calculation via JNI | JNI naming convention, JNI_OnLoad |
nativefeature/CMakeLists.txt | CMake build for native library | C++17, libnative_bobbing.so |
nativefeature/build.gradle.kts | Library module with CMake | externalNativeBuild.cmake, maven-publish |
android.library plugin combined with meta.spatial.plugin. Feature modules depend only on meta-spatial-sdk-base and meta-spatial-sdk-toolkit, avoiding app-specific dependencies. The registrationsClassName setting in build.gradle.kts controls the name of the generated component registration class, preventing classpath conflicts when multiple modules define components.// kotlinfeature/build.gradle.kts
spatial {
components {
registrationsClassName = "PulsingFeatureComponentRegistrations"
}
}
src/main/components/components.xml are processed by the Gradle plugin at build time into generated Kotlin data classes and a registration object. The generated object has an all() method returning List<ComponentRegistration>.<!-- kotlinfeature/components.xml -->
<Component name="Pulsing">
<FloatAttribute name="minScale" defaultValue="0.8f" />
<FloatAttribute name="maxScale" defaultValue="1.2f" />
<FloatAttribute name="frequency" defaultValue="1.0f" />
</Component>
componentsToRegister():// PulsingFeature.kt
override fun componentsToRegister(): List<ComponentRegistration> {
return PulsingFeatureComponentRegistrations.all()
}
PulsingFeature is a pure Kotlin implementation. It overrides componentsToRegister() and systemsToRegister() without requiring native libraries. In contrast, NativeBobbingFeature uses preRuntimeOnCreate() to load a native library and includes a CMake build for the C++ code.// NativeBobbingFeature.kt
override fun preRuntimeOnCreate(savedInstanceState: Bundle?) {
loadLibrary("native_bobbing")
}
std::sin. For the complete native implementation, see native_bobbing.cpp.Query.where { has(...) }.eval() to find entities that have the specified components. The query returns a Sequence<Entity> that the system iterates to apply logic.// PulsingSystem.kt
val query = Query.where { has(Pulsing.id) }
for (entity in query.eval()) {
val pulsing = entity.getComponent<Pulsing>()
entity.setComponent(Scale(currentScale))
}
maven-publish with publishToMavenLocal support. This allows you to publish features to a Maven repository and consume them from other projects.// kotlinfeature/build.gradle.kts
register<MavenPublication>("release") {
artifactId = "pulsing-feature"
afterEvaluate { from(components["release"]) }
}
./gradlew :kotlinfeature:publishToMavenLocal.getDependencies() to declare that one feature requires another, ensuring correct initialization orderearlySystemsToRegister() or lateSystemsToRegister() to control system execution order relative to other systems