My
Bevy
mobile app is very incomplete, but I would like to be able to distribute test builds early on. That means Android builds need to be acceptable for the Play Store and iOS builds need to pass Apple's bar for the App Store. On the way to that goal, there are a couple technical issues to solve and some configuration to do in App Store Connect and Google Play Console. I will concentrate on the technical challenges while mentioning some store configurations.
Most of this effort did not directly go into my current app project. I decided to add iOS and Android support to
bevy_game_template
first. This way, the community might benefit more from my work and I can just copy the setup for other projects.
The cargo-apk setup from Bevy's mobile example allows us to build APK files. The Play Store no longer accepts APKs, but
requires app bundles
. This makes sense to keep downloads small and still support multiple device ABIs (
A
pplication
B
inary
I
nterface
), but it requires some extra work to get an app bundle for a Bevy game.
The only tool I could convince to create an app bundle from my Bevy project is
xbuild
, the WIP successor of cargo-apk
1
. We can configure xbuild to bundle our project for Android with the following
manifest.yaml
Currently, xbuild decides to create app bundles only when using
gradle
:
true
and only for production builds. Running
x build --platform android --store play
creates an app bundle, but derived APKs instantly crash.
I had to do a couple changes to get xbuild to create working app bundles. Some of them might get upstreamed, but a few are obvious hacks that only work for the Bevy project structure (like a single, hardcoded
assets
directory). For the xcode commands in this post to work as expected, you need to install xbuild from my fork:
cargo
install
--git
https://github.com/NiklasEi/xbuild
.
libc++ shared is missing
Looking at the logs with
adb logcat
while trying to start the app on my phone yields the following error:
java.lang.UnsatisfiedLinkError: Unable to load native library "/data/app/~~0cxWIprct-rKy7Vggu9E4g==/me.nikl.bevygame-YTVT_qxOwHzm_kGsGV9cYw==/lib/arm64/libmobile.so": dlopen failed: library "libc++_shared.so" not found
The helpful line among hundreds of unhelpful ones.
Opening the APK as an archive indeed shows that
lib/arm64-v8a/
does not include
libc++_shared.so
, while an APK built with cargo-apk includes it. This library is required for audio support in Bevy. I probably could have just ripped out audio for now and moved on, but who am I kidding? I want audio!
Telling gradle to include
libc++_shared.so
turned out to be more complicated than I had thought. Usually, it should realize on its own that the library is needed and include it. The problem is, that we are not using gradle to build the game library, but cargo. So gradle has no way of knowing that we need
libc++_shared.so
. In the end, I gave up on a "clean" solution and went with a hack. I basically
told gradle that the project includes a c++ library
which requires the shared library. The c++ project is empty, but it does the trick and creates minimal bloat (~4kB).
With this change, the
UnsatisfiedLinkError
from above is gone, but the game still doesn't work.
Include assets
While cargo-apk allows simply configuring an asset directory, xbuild does not jet support that. There is an
open PR to add support for assets
to the non-gradle builds, but that only works for APKs
2
.
Since Bevy usually comes with a single
assets
directory in the root of the project,
I told gradle to just include that
with a hardcoded path from deep inside the target directory. At this point, the build generated by xbuild using gradle runs properly.
Continuous deployment
The app bundle needs to be signed with jarsigner and can then be uploaded to Google Play Console. Doing this manually for every version is no fun, so I wrote a
GitHub workflow for it
.
Support more Android devices
After uploading an app bundle, we can use the app bundle explorer in the Google Play Console to check bundle exports for different devices. My early bundles contained libraries for 4 different ABIs due to the dummy c++ library and
gradle compiling for all non-deprecated ABIs by default
. The game, on the other hand, was only compiled for
arm64-v8a
, which was hardcoded in xbuild.
The bundle still contains lib directories for
x86
and
x86_64
due to the "dummy cpp lib" workaround. Those lib directories tell the play store that our app bundle can generate APKs for those ABIs which is not correct since we do not build our game for them. A
small gradle configuration
tells it to not build the dummy library for the
x86(64)
ABIs.
I don't own a Mac or iPhone, but still want to support iOS. So I borrowed a Mac for a couple of days to get everything set up in
bevy_game_template
.
The first time I tried to let Xcode publish the app with the copied setup from the Bevy mobile example, the process ended in a list of errors from the App Store. The required changes boiled down to adding an app icon and a launch screen.
Our app requires a launch screen. The
Info.plist
from the Bevy mobile example includes an entry for
UILaunchStoryboardName
, but the launch screen itself is not included. I created one in Xcode following a
stack overflow answer
that just contains a centered icon.
Continuous deployment
At this point, Xcode can create app builds and push them to App Store Connect. I spent some time on creating a
GitHub workflow
to automate the process. The iOS workflow requires a bit more setup than the Android one, but hopefully, I don't need to organize a Mac again anytime soon.
One issue left...
...the app has no audio on iOS. I have only tested this with
bevy_kira_audio
so far, so
bevy_audio
might be fine. Maybe someone else knows what's going on here?
Getting ready for a release
Both stores require some setup for the store pages. The app from
bevy_game_template
is not supposed to go live in the app stores, but it would be nice to be able to share a link that allows anyone to install it. For iOS the goal is "external testing" in AppFlight. In Google Play this release type is called "open testing". Both of these programs require the app to go through a review and the store page to be mostly complete.
Getting screenshots
Screenshots are a big part of finishing the required setup for publishing. Luckily, the last Bevy update came with a very handy screenshot API. Setting the window dimension and adding the below system gives nice screenshots in configurable resolution.