Skip to content

Commit ae5a410

Browse files
committed
examples: Use single up-to-date android example
Objective - Only use a single modern and better documented android example - Fix #10945 - Fix #17122 Problems with current examples - We are currently using deprecated functions like for example [setSystemUiVisibility(int)](https://developer.android.com/reference/android/view/View#setSystemUiVisibility(int)). - Most Android dependencies are very out of date. - Kotlin is the [preffered language](https://developer.android.com/kotlin/first) for Android and we should migrate the examples to it. - Most of the code in the examples is not inherently clear and there are very few comments especially in the build scripts. - We should only have a single Android example using `games-activity` and `cargo-ndk`. It is confusing and unnecessary to do it any other way in my opinion. - The crash from #10945 is fixed by migrating to `games-activity` 4. Before this PR, we were using `games-activity` 2. #17122 is apparently also fixed because of this. Solution - Replace all android examples with `./examples/mobile/android` - Update dependencies in `libs.versions.toml` - Add `gradle-daemon-jvm.properties` generated with `./gradlew updateDaemonJvm` - Update gradle wrapper - Migrate everything to kotlin/kotlin build scripts - Use supported functions instead of deprecated ones - Move `/assets/ic_launcher.png` to `/examples/mobile/android/app/src/main/res/mipmap/ic_launcher.png` because that is where it is [meant to be stored](https://developer.android.com/guide/topics/resources/providing-resources). - Use `--link-libcxx-shared` flag for `cargo-ndk` in `examples/README.md` - Use `targetSdk = 36` and `compileSdk = 36` and explain in `examples/README.md`
1 parent f1f57f6 commit ae5a410

40 files changed

Lines changed: 584 additions & 1104 deletions

.github/workflows/validation-jobs.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,11 @@ jobs:
9999
run: cargo install --force cargo-ndk
100100

101101
- name: Build .so file
102-
run: cargo ndk -t arm64-v8a -P 26 -o android_example/app/src/main/jniLibs build --package bevy_mobile_example
102+
# FIXME: Remove --release flag after an issue that makes it necessary for building the example has been resolved.
103+
run: cargo ndk build --link-libcxx-shared -t aarch64-linux-android -p bevy_mobile_example --release -o ./examples/mobile/android/app/src/main/jniLibs
103104

104105
- name: Build app for Android
105-
run: cd examples/mobile/android_example && chmod +x gradlew && ./gradlew build
106+
run: cd ./examples/mobile/android && chmod +x ./gradlew && ./gradlew build
106107

107108
run-examples-on-wasm:
108109
if: ${{ github.event_name == 'merge_group' }}

docs-template/EXAMPLE_README.md.tpl

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,8 @@ git checkout v0.4.0
4545
- [Android](#android)
4646
- [Setup](#setup)
4747
- [Build & Run](#build--run)
48-
- [About `libc++_shared.so`](#about-libc_sharedso)
48+
- [Debugging](#debugging)
4949
- [Old phones](#old-phones)
50-
- [About `cargo-apk`](#about-cargo-apk)
5150
- [iOS](#ios)
5251
- [Setup](#setup-1)
5352
- [Build & Run](#build--run-1)
@@ -104,16 +103,20 @@ Alternatively, you can install Android Studio.
104103

105104
#### Build & Run
106105

106+
<!-- FIXME: Remove --release flag after an issue that makes it necessary for building the example has been resolved.-->
107+
108+
**⚠️ Note:** The `--release` flag is currently necessary for the example. You might be able to omit it when building your own crate.
109+
107110
To build an Android app, you first need to build shared object files for the target architecture with `cargo-ndk`:
108111

109112
```sh
110-
cargo ndk -t <target_name> -P 26 -o <project_name>/app/src/main/jniLibs build
113+
cargo ndk build --link-libcxx-shared -t <target_name> --release -o <project_path>/android/app/src/main/jniLibs
111114
```
112115

113116
For example, to compile to a 64-bit ARM platform:
114117

115118
```sh
116-
cargo ndk -t arm64-v8a -P 26 -o android_example/app/src/main/jniLibs build
119+
cargo ndk build --link-libcxx-shared -t aarch64-linux-android --release -o ./android/app/src/main/jniLibs
117120
```
118121

119122
Setting the output path ensures the shared object files can be found in target-specific directories under `jniLibs` where the JNI can find them.
@@ -123,21 +126,14 @@ See the `cargo-ndk` [README](https://crates.io/crates/cargo-ndk) for other optio
123126
After this you can build it with `gradlew`:
124127

125128
```sh
129+
cd ./android
126130
./gradlew build
127131
```
128132

129133
Or build it with Android Studio.
130134

131135
Then you can test it in your Android project.
132136

133-
##### About `libc++_shared.so`
134-
135-
Bevy may require `libc++_shared.so` to run on Android, as it is needed by the `oboe` crate, but typically `cargo-ndk` does not copy this file automatically.
136-
137-
To include it, you can manually obtain it from NDK source or use a `build.rs` script for automation, as described in the `cargo-ndk` [README](https://github.com/bbqsrc/cargo-ndk?tab=readme-ov-file#linking-against-and-copying-libc_sharedso-into-the-relevant-places-in-the-output-directory).
138-
139-
Alternatively, you can modify project files to include it when building an APK. To understand the specific steps taken in this project, please refer to the comments within the project files for detailed instructions(`app/CMakeList.txt`, `app/build.gradle`, `app/src/main/cpp/dummy.cpp`).
140-
141137
#### Debugging
142138

143139
You can view the logs with the following command:
@@ -156,28 +152,18 @@ adb uninstall org.bevyengine.example
156152

157153
#### Old phones
158154

159-
In its examples, Bevy targets the minimum Android API that Play Store <!-- markdown-link-check-disable -->
160-
[requires](https://developer.android.com/distribute/best-practices/develop/target-sdk) to upload and update apps. <!-- markdown-link-check-enable -->
161-
Users of older phones may want to use an older API when testing. By default, Bevy uses [`GameActivity`](https://developer.android.com/games/agdk/game-activity), which only works for Android API level 31 and higher, so if you want to use older API, you need to switch to `NativeActivity`.
155+
**⚠️ Note:** If you are using `bevy_audio` the minimum supported Android API version is 26 (Android 8/Oreo).
156+
157+
In its example, Bevy uses Android API 36 as `targetSdk` to be able to benefit from security and performance improvements. For backwards compatibility, the example specifies Android API 31 as `minSdk`. This approach is recommended in the [Android Developers documentation](https://developer.android.com/google/play/requirements/target-sdk#why-target).
162158

163-
Keep in mind that if you are using `bevy_audio` the minimum supported Android API version is 26 (Android 8/Oreo).
159+
Users of older phones may want to use an older API when testing. By default, Bevy uses [`GameActivity`](https://developer.android.com/games/agdk/game-activity), which only works for Android API 31 and higher, so if you want to use an older API, you need to switch to [`NativeActivity`](https://developer.android.com/reference/android/app/NativeActivity).
164160

165-
To use `NativeActivity`, you need to edit it in `cargo.toml` manually like this:
161+
To use `NativeActivity`, you need to write a custom `MainActivity.kt` using `NativeActivity` instead of `GameActivity` and add the `android-native-activity` feature to Bevy in your `Cargo.toml` like this:
166162

167163
```toml
168-
bevy = { version = "0.19", default-features = false, features = ["android-native-activity", ...] }
164+
bevy = { version = "0.19", default-features = false, features = ["android-native-activity", <...>] }
169165
```
170166

171-
Then build it as the [Build & Run](#build--run) section stated above.
172-
173-
##### About `cargo-apk`
174-
175-
You can also build an APK with `cargo-apk`, a simpler and deprecated tool which doesn't support `GameActivity`. If you want to use this, there is a [folder](./mobile/android_basic) inside the mobile example with instructions.
176-
177-
Example | File | Description
178-
--- | --- | ---
179-
`android` | [`mobile/src/lib.rs`](./mobile/src/lib.rs) | A 3d Scene with a button and playing sound
180-
181167
### iOS
182168

183169
#### Setup

examples/README.md

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,8 @@ git checkout v0.4.0
7777
- [Android](#android)
7878
- [Setup](#setup)
7979
- [Build & Run](#build--run)
80-
- [About `libc++_shared.so`](#about-libc_sharedso)
80+
- [Debugging](#debugging)
8181
- [Old phones](#old-phones)
82-
- [About `cargo-apk`](#about-cargo-apk)
8382
- [iOS](#ios)
8483
- [Setup](#setup-1)
8584
- [Build & Run](#build--run-1)
@@ -702,16 +701,20 @@ Alternatively, you can install Android Studio.
702701

703702
#### Build & Run
704703

704+
<!-- FIXME: Remove --release flag after an issue that makes it necessary for building the example has been resolved.-->
705+
706+
**⚠️ Note:** The `--release` flag is currently necessary for the example. You might be able to omit it when building your own crate.
707+
705708
To build an Android app, you first need to build shared object files for the target architecture with `cargo-ndk`:
706709

707710
```sh
708-
cargo ndk -t <target_name> -P 26 -o <project_name>/app/src/main/jniLibs build
711+
cargo ndk build --link-libcxx-shared -t <target_name> --release -o <project_path>/android/app/src/main/jniLibs
709712
```
710713

711714
For example, to compile to a 64-bit ARM platform:
712715

713716
```sh
714-
cargo ndk -t arm64-v8a -P 26 -o android_example/app/src/main/jniLibs build
717+
cargo ndk build --link-libcxx-shared -t aarch64-linux-android --release -o ./android/app/src/main/jniLibs
715718
```
716719

717720
Setting the output path ensures the shared object files can be found in target-specific directories under `jniLibs` where the JNI can find them.
@@ -721,21 +724,14 @@ See the `cargo-ndk` [README](https://crates.io/crates/cargo-ndk) for other optio
721724
After this you can build it with `gradlew`:
722725

723726
```sh
727+
cd ./android
724728
./gradlew build
725729
```
726730

727731
Or build it with Android Studio.
728732

729733
Then you can test it in your Android project.
730734

731-
##### About `libc++_shared.so`
732-
733-
Bevy may require `libc++_shared.so` to run on Android, as it is needed by the `oboe` crate, but typically `cargo-ndk` does not copy this file automatically.
734-
735-
To include it, you can manually obtain it from NDK source or use a `build.rs` script for automation, as described in the `cargo-ndk` [README](https://github.com/bbqsrc/cargo-ndk?tab=readme-ov-file#linking-against-and-copying-libc_sharedso-into-the-relevant-places-in-the-output-directory).
736-
737-
Alternatively, you can modify project files to include it when building an APK. To understand the specific steps taken in this project, please refer to the comments within the project files for detailed instructions(`app/CMakeList.txt`, `app/build.gradle`, `app/src/main/cpp/dummy.cpp`).
738-
739735
#### Debugging
740736

741737
You can view the logs with the following command:
@@ -754,28 +750,18 @@ adb uninstall org.bevyengine.example
754750

755751
#### Old phones
756752

757-
In its examples, Bevy targets the minimum Android API that Play Store <!-- markdown-link-check-disable -->
758-
[requires](https://developer.android.com/distribute/best-practices/develop/target-sdk) to upload and update apps. <!-- markdown-link-check-enable -->
759-
Users of older phones may want to use an older API when testing. By default, Bevy uses [`GameActivity`](https://developer.android.com/games/agdk/game-activity), which only works for Android API level 31 and higher, so if you want to use older API, you need to switch to `NativeActivity`.
753+
**⚠️ Note:** If you are using `bevy_audio` the minimum supported Android API version is 26 (Android 8/Oreo).
754+
755+
In its example, Bevy uses Android API 36 as `targetSdk` to be able to benefit from security and performance improvements. For backwards compatibility, the example specifies Android API 31 as `minSdk`. This approach is recommended in the [Android Developers documentation](https://developer.android.com/google/play/requirements/target-sdk#why-target).
760756

761-
Keep in mind that if you are using `bevy_audio` the minimum supported Android API version is 26 (Android 8/Oreo).
757+
Users of older phones may want to use an older API when testing. By default, Bevy uses [`GameActivity`](https://developer.android.com/games/agdk/game-activity), which only works for Android API 31 and higher, so if you want to use an older API, you need to switch to [`NativeActivity`](https://developer.android.com/reference/android/app/NativeActivity).
762758

763-
To use `NativeActivity`, you need to edit it in `cargo.toml` manually like this:
759+
To use `NativeActivity`, you need to write a custom `MainActivity.kt` using `NativeActivity` instead of `GameActivity` and add the `android-native-activity` feature to Bevy in your `Cargo.toml` like this:
764760

765761
```toml
766-
bevy = { version = "0.19", default-features = false, features = ["android-native-activity", ...] }
762+
bevy = { version = "0.19", default-features = false, features = ["android-native-activity", <...>] }
767763
```
768764

769-
Then build it as the [Build & Run](#build--run) section stated above.
770-
771-
##### About `cargo-apk`
772-
773-
You can also build an APK with `cargo-apk`, a simpler and deprecated tool which doesn't support `GameActivity`. If you want to use this, there is a [folder](./mobile/android_basic) inside the mobile example with instructions.
774-
775-
Example | File | Description
776-
--- | --- | ---
777-
`android` | [`mobile/src/lib.rs`](./mobile/src/lib.rs) | A 3d Scene with a button and playing sound
778-
779765
### iOS
780766

781767
#### Setup
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#
2+
# https://help.github.com/articles/dealing-with-line-endings/
3+
#
4+
# Linux start script should use lf
5+
/gradlew text eol=lf
6+
7+
# These are Windows script files and should use crlf
8+
*.bat text eol=crlf
9+
10+
# Binary files should be left untouched
11+
*.jar binary
12+
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
# This is part of a hack to convince gradle to insert libc++_shared.so
2-
cmake_minimum_required(VERSION 3.4.1)
3-
project(cppshared_dummy)
4-
set (SRC_DIR ./src/main/cpp)
5-
add_library (dummy SHARED ${SRC_DIR}/dummy.cpp)
1+
# This is part of a hack to convince gradle to insert libc++_shared.so
2+
cmake_minimum_required(VERSION 3.4.1)
3+
project(cppshared_dummy)
4+
set(SRC_DIR ./src/main/cpp)
5+
add_library(dummy SHARED ${SRC_DIR}/dummy.cpp)
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
plugins {
2+
alias(libs.plugins.android.application)
3+
}
4+
5+
kotlin {
6+
compilerOptions {
7+
languageVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_3
8+
jvmToolchain(8)
9+
}
10+
}
11+
12+
android {
13+
namespace = "org.bevyengine.example"
14+
compileSdk = 36
15+
16+
// https://developer.android.com/reference/tools/gradle-api/9.1/com/android/build/api/dsl/DefaultConfig
17+
defaultConfig {
18+
applicationId = "org.bevyengine.example"
19+
minSdk = 31
20+
targetSdk = 36
21+
// NOTE: Increase by 1 on each release
22+
versionCode = 1
23+
// NOTE: Update with full semantic version on each release
24+
versionName = "0.0.0"
25+
// https://developer.android.com/reference/tools/gradle-api/9.1/com/android/build/api/variant/ExternalNativeBuild
26+
// NOTE: We need this, otherwise libc++_shared.so will not be inserted
27+
@Suppress("UnstableApiUsage")
28+
externalNativeBuild {
29+
cmake {
30+
arguments("-DANDROID_STL=c++_shared")
31+
}
32+
}
33+
// https://developer.android.com/reference/tools/gradle-api/9.1/com/android/build/api/dsl/Ndk
34+
ndk {
35+
abiFilters.addAll(listOf("arm64-v8a", "armeabi-v7a", "x86_64"))
36+
}
37+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
38+
}
39+
// https://developer.android.com/reference/tools/gradle-api/9.1/com/android/build/api/dsl/ExternalNativeBuild
40+
externalNativeBuild {
41+
cmake {
42+
path = file("CMakeLists.txt")
43+
}
44+
}
45+
// https://developer.android.com/reference/tools/gradle-api/9.1/com/android/build/api/dsl/BuildType
46+
buildTypes {
47+
getByName("release") {
48+
// https://developer.android.com/topic/performance/app-optimization/enable-app-optimization
49+
isMinifyEnabled = true
50+
isShrinkResources = true
51+
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"))
52+
}
53+
}
54+
// https://developer.android.com/reference/tools/gradle-api/9.1/com/android/build/api/dsl/CompileOptions
55+
compileOptions {
56+
sourceCompatibility = JavaVersion.VERSION_1_8
57+
targetCompatibility = JavaVersion.VERSION_1_8
58+
}
59+
// https://developer.android.com/reference/tools/gradle-api/9.1/com/android/build/api/dsl/BuildFeatures
60+
buildFeatures {
61+
prefab = true
62+
}
63+
packaging {
64+
// https://developer.android.com/reference/tools/gradle-api/9.1/com/android/build/api/dsl/JniLibsPackaging
65+
jniLibs.excludes.add("lib/*/libdummy.so")
66+
jniLibs.pickFirsts.add("lib/*/libc++_shared.so")
67+
}
68+
// https://developer.android.com/reference/tools/gradle-api/9.1/com/android/build/api/dsl/AndroidSourceSet
69+
sourceSets {
70+
getByName("main") {
71+
assets {
72+
directories += "../../../../assets"
73+
}
74+
}
75+
}
76+
}
77+
78+
dependencies {
79+
implementation(libs.appcompat)
80+
implementation(libs.core)
81+
implementation(libs.material)
82+
implementation(libs.games.activity)
83+
implementation(libs.core.ktx)
84+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools">
4+
5+
<application android:icon="@mipmap/ic_launcher" android:label="Bevy Example" android:roundIcon="@mipmap/ic_launcher" android:theme="@style/Theme.AppCompat.NoActionBar" tools:targetApi="36">
6+
<activity android:name=".MainActivity" android:exported="true" android:configChanges="layoutDirection|locale|orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode" android:theme="@style/Theme.AppCompat.NoActionBar">
7+
<meta-data android:name="android.app.lib_name" android:value="bevy_mobile_example" />
8+
<intent-filter>
9+
<action android:name="android.intent.action.MAIN" />
10+
<category android:name="android.intent.category.LAUNCHER" />
11+
</intent-filter>
12+
</activity>
13+
</application>
14+
15+
</manifest>

examples/mobile/android_example/app/src/main/cpp/dummy.cpp renamed to examples/mobile/android/app/src/main/cpp/dummy.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
// Intentionally blank -- this is a dummy code!
2-
// This is part of a hack to convince gradle to insert libc++_shared.so
1+
// Intentionally blank -- this is a dummy code!
2+
// This is part of a hack to convince gradle to insert libc++_shared.so
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.bevyengine.example
2+
3+
import androidx.core.view.WindowCompat
4+
import androidx.core.view.WindowInsetsCompat
5+
import androidx.core.view.WindowInsetsControllerCompat
6+
import com.google.androidgamesdk.GameActivity
7+
8+
/**
9+
* Load rust library and handle android specifics to integrate with it.
10+
*
11+
*
12+
* The library is loaded at class initialization and provided by jniLibs.
13+
*/
14+
class MainActivity : GameActivity() {
15+
/**
16+
* Hide system UI if the app window is focused.
17+
*
18+
*
19+
* Called when the current Window of the activity gains or loses focus.
20+
*/
21+
override fun onWindowFocusChanged(hasFocus: Boolean) {
22+
// Call parent class implementation of onWindowFocusChanged to make sure that we are updating correctly.
23+
super.onWindowFocusChanged(hasFocus)
24+
25+
if (hasFocus) {
26+
hideSystemUi()
27+
}
28+
}
29+
30+
/**
31+
* Hide system UI.
32+
*/
33+
private fun hideSystemUi() {
34+
val windowInsetsController =
35+
WindowCompat.getInsetsController(window, window.decorView)
36+
37+
// Show bars if swiping
38+
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
39+
// Hide both the status bar and the navigation bar.
40+
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
41+
}
42+
43+
companion object {
44+
// Load rust library
45+
init {
46+
System.loadLibrary("bevy_mobile_example")
47+
}
48+
}
49+
}

assets/android-res/mipmap-mdpi/ic_launcher.png renamed to examples/mobile/android/app/src/main/res/mipmap/ic_launcher.png

File renamed without changes.

0 commit comments

Comments
 (0)