cpp: add Android backend support#11628
Conversation
Enable C++ developers to build Slint applications for Android using the standard Gradle + CMake workflow or standalone CMake with the NDK toolchain. The implementation adds: - `backend-android-activity` feature to slint-cpp that wires up the existing Rust Android backend and provides an `android_main` entry point which calls `extern "C" slint_main()` in user C++ code - CMake auto-configuration that detects the Android NDK toolchain, maps ANDROID_ABI to Rust targets, enables Skia renderer, and propagates NDK/SDK environment variables to the Rust build - A Gradle template project (api/cpp/android/) similar to the ESP-IDF template, with AndroidManifest.xml, build.gradle.kts, and a minimal hello-world example - An Android build of the C++ todo example (examples/todo/cpp/android/) that has been tested as a working APK on a real device - Documentation covering prerequisites, project setup, building, deploying, JNI interop, and troubleshooting - Updates to existing mobile docs removing the "Rust only" limitation Also fixes two pre-existing issues that prevented C++ compilation with Android NDK r27: - slint_callbacks.h: std::apply with const tuple& fails on NDK r27's strict libc++; replaced with a custom detail::apply helper - cbindgen.rs: Option<Timer> was forward-declared but never defined for Android-only ContextMenu::long_press_timer field Issue: slint-ui#6281
|
|
|
Can you also port the usecase demo to android, and have it build by the CI. |
- Fix CI: add missing backend_android_activity field in xtask/cppdocs.rs - Fix Timer binary compatibility: use uintptr_t instead of uint64_t in C++ Timer, remove Option wrapper from ContextMenu::long_press_timer - Remove Option<T> template hack from cbindgen.rs - Add comment explaining custom detail::apply (NDK r27 compat) - Merge android-cpp.mdx into android.mdx using Tabs for Rust/C++ - Share todo example main.cpp/CMakeLists.txt between desktop and Android - Port usecases demo to Android with gradle project - Add android_cpp CI job for cross-compiling usecases demo - Remove ANDROID_CPP.md from tracked files - Add note that backend-android-activity is auto-enabled by CMake
- Add .kts and .properties to xtask license header checker - Add SPDX headers to gradle-wrapper.properties files - Add SPDX header to todo AndroidManifest.xml - Add REUSE.toml annotations for gradle-wrapper.properties files
Build Slint from the top-level CMakeLists.txt with SLINT_BUILD_EXAMPLES for the android_cpp CI job so that the Slint target is available when building the usecases demo. Also add the usecases subdirectory to demos/CMakeLists.txt and fix cSpell unknown words (armeabi, androideabi) in the Android docs.
|
|
||
| namespace detail { | ||
| // Custom apply implementation to replace std::apply. | ||
| // NDK r27's libc++ rejects std::apply with const tuple references in some configurations. |
There was a problem hiding this comment.
Why? is that some specific version of libc++? is there some issue for that?
Could there be another fix that doesn't need this workaround?
There was a problem hiding this comment.
This is an issue with NDK r27's libc++ (the version shipped with the current LTS NDK). When std::apply is called with a const std::tuple&, it fails to compile because their implementation tries to use std::get on a non-forwarding reference in a way that triggers a substitution failure.
I don't have a specific upstream issue link for this — I hit it during testing with NDK r27 (27.2.12829862). The custom detail::apply is a straightforward reimplementation that avoids the problematic code path.
An alternative would be to avoid passing const tuple refs to std::apply in the call sites, but that would require changing the Callback implementation more invasively. This seemed like the least disruptive fix. Happy to explore other approaches if you'd prefer.
|
Done — the usecases demo has been ported to Android with a full gradle project in |
The backend-android-activity Cargo feature is unnecessary. The i-slint-backend-android-activity dependency is now unconditionally included on Android via target cfg, and the selector feature is enabled unconditionally (no-op on non-Android). CMake no longer needs to force-enable the feature.
The template will be set up as a branch in slint-ui/slint-cpp-template instead, per review feedback.
Timer is already visible inside slint::cbindgen_private through normal C++ name lookup in the enclosing slint namespace.
|
|
||
| distributionBase=GRADLE_USER_HOME | ||
| distributionPath=wrapper/dists | ||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip |
There was a problem hiding this comment.
From a maintenance point of view, i wonder if this is good to have this kind of file repeated for all our example. And when do we need to update this and other stuff. Could you suggest easier method than having all these files?
There was a problem hiding this comment.
The main files that get duplicated are: gradle-wrapper.properties (pinned gradle version), gradle.properties (just android.useAndroidX=true), settings.gradle.kts (project name), and build.gradle.kts (AGP plugin version). These are fairly stable — gradle version bumps happen infrequently.
One option to reduce duplication: a shared top-level android/ directory with a single gradle project that builds all examples as subprojects, each referencing their root CMakeLists.txt. This would consolidate the gradle wrapper and AGP version in one place. Want me to try that approach?
- Remove redundant android-specific CMakeLists.txt files; root CMakeLists now supports SLINT_SOURCE_DIR for building Slint from source via gradle - Update gradle build.gradle.kts to reference root CMakeLists - Invert directory tree in docs: C++ code at root, android/ as subdirectory - Remove outdated api/cpp/android glob from REUSE.toml
- Move Rust API docs link inside Rust tab - Simplify C++ section to reference existing CMake docs - Remove redundant .slint file example - Simplify slint_main documentation - Streamline project structure description
| endif() | ||
|
|
||
| # Android requires a shared library | ||
| set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE) |
There was a problem hiding this comment.
I wonder if these defaults could be set at the source instead of here with these kind of ugly overrides. We're in the right file, aren't we?:)
There was a problem hiding this comment.
Agree. Although these are not default but mandatory features
There was a problem hiding this comment.
Updated the comment to say "Android requires the skia renderer and cannot use desktop backends".
There was a problem hiding this comment.
We could integrate it into the define_cargo_dependent_feature calls by adding AND NOT ANDROID to the depends_condition for winit/qt/femtovg, and using if(ANDROID) to flip the renderer-skia default to ON. The issue is that these are mandatory for Android, not just defaults — with the current approach, FORCE prevents the user from accidentally enabling winit on Android. With the depends_condition approach, we get the same effect since cmake_dependent_option forces the value OFF when the condition is false.
Want me to try that approach? It would replace the FORCE overrides with conditions on the feature definitions themselves.
There was a problem hiding this comment.
If mandatory perhaps that should become an option in the macro/function. But IMO it's sufficient if defaults are on/off correctly and it's all in one place.
My concern is that if we remove/rename the feature we need to look at multiple places.
(Not a super big issue but worth research if it can be done better)
| endif() | ||
|
|
||
| # Android requires a shared library | ||
| set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE) |
There was a problem hiding this comment.
Agree. Although these are not default but mandatory features
- Flatten usecases gradle project: remove app/ submodule, merge into single-module project with all config in root build.gradle.kts - Update docs to show flattened android project structure with full file contents for build.gradle.kts and AndroidManifest.xml - Move Rust API docs link to page intro (not Rust-specific section) - Fix CMakeLists comment: android features are mandatory, not defaults
- Revert SLINT_SOURCE_DIR addition from example CMakeLists files, keeping them simple with just find_package(Slint REQUIRED) - Fix broken docs link: getting_started → getting-started
Summary
Enable C++ developers to build Slint applications for Android, closing #6281.
backend-android-activityfeature to slint-cpp withandroid_mainbridge that calls user'sextern "C" slint_main()ANDROID_ABIto Rust targets, enables Skia renderer, propagates NDK/SDK env varsapi/cpp/android/) for easy project bootstrappingexamples/todo/cpp/android/), tested as a working APK on a real deviceAlso fixes two pre-existing issues blocking C++ Android compilation:
slint_callbacks.h:std::applywith const tuple ref fails on NDK r27's stricter libc++; replaced with customdetail::applycbindgen.rs:Option<Timer>was forward-declared but never defined for Android-onlyContextMenu::long_press_timerfieldTest plan