Miscellaneous hints for developers¶
Never replace what was already distributed¶
Replacing an already published release, or APKs attached to it, can come with several side effects. To mention the most obvious:
- folks who manually install from your releases, might not notice replaced APKs if they see no new releases
- whatever goes by tags/releases to fetch the latest update, would not process the same tag/release again (e.g. our repo updater, our builders)
- independent builders checking for Reproducible Builds, having processed the original tag/release/APK, will not process the replaced one – so their confirmation would be void (as checksums of the APKs no longer match)
If something went wrong, instead consider the following:
- remove the APK from an existing release if it e.g. has serious issues (e.g. security problems, instant crashes)
- make a new release, with
versionCodeincreased, andversionNamehaving a higher „patch level“ (see semantic versioning). The tag name should correspond to the newversionNamethen, of course.
Implied permissions¶
Sometimes, various permissions show up in the final AndroidManifest.xml of your Android app, and you wonder where they come from. Apart from dependencies having them declared, there are also permissions which are implied, based on SDK versions:
| Importing a library | Declares this permission in the merged application manifest |
|---|---|
minSdk < 2 |
WRITE_EXTERNAL_STORAGE |
minSdk < 4 |
WRITE_EXTERNAL_STORAGE, READ_PHONE_STATE |
Declared WRITE_EXTERNAL_STORAGE |
READ_EXTERNAL_STORAGE |
minSdk < 16 andDeclared READ_CONTACTS |
READ_CALL_LOG |
minSdk < 16 andDeclared WRITE_CONTACTS |
WRITE_CALL_LOG |
(source: this article, via Warren Bank; also see aapt/Command.cpp)
Android libraries declare a minSdk, but they do not declare a targetSdk (the com.android.library gradle plugin deprecated and later removed the targetSdk property). When an application is compiled and the manifests for its libraries are merged:
- the
targetSdkof a library is set to itsminSdk - depending of that
targetSdk, implied permissions are then added to the application, as outlined in above table.
So if e.g. android.permission.READ_PHONE_STATE shows up unexpectedly with your app, check if any of your libraries has no minSdk declared (which would be considered equal to minSdk:0).
Fastlane and Translation Services¶
Translators are often unaware of consequences when exceeding character limits (hint: all text behind the limit will simply be truncated, even mid-word), and thus commit „longer strings“ even when told not to. Luckily, some translation services provide means to enforce such limits.
Weblate¶
Weblate allows to set „enforcement checks“ for string lengths. In your Weblate component, go to Operations › Settings › Translations › Enforced checks and make sure „Maximum length of translation“ is on the right side. Then, block ignored checks from getting committed.
Despite what you would think from the description of „enforced“, users can still just hit submit a second time to force-submit a translation. So you need to prevent those from being committed. For this, in your Weblate project, go to Operations › Settings › Workflow › Translation quality filter and make sure it is set to „Skip translations marked as needing editing“. This keeps them from being committed to git.
After you've done both it should work as intended to prevent new incorrect submissions.
(source: this issue comment by Sylvia)
Crowdin¶
Crowdin also allows you to set a maximum translation length, see Adding Strings and String Editing in their documentation.
Clean builds when building locally¶
As the „first basic rule“ of our RB Hints for Developers states, one should always build release APKs „from a clean tree at exactly the commit your release-tag will point to”. A CI (such as a Forgejo or GitHub actions) is the best way to achieve this. But what if you build locally? It's too easy to forget about „that one file“ you've just added (or modified last-minute). Or the artifacts left in the tree from prior builds.
If you're on a Linux machine (or another environment running Bash), you can use a simple script to help you with this:
cd $project_dir
if [[ $(git status --porcelain | wc -l) -ne 0 ]]; then
>&2 echo "ERROR! Tree is dirty, aborting build."
exit 1
fi
./gradlew clean assembleRelease
Make sure to replace $project_dir with the directory your code resides (and the APK is built in) – and to adjust the build command (e.g. if you use build flavors). Short explanation:
- the first block will abort the script if there are uncommitted changes. The "ERROR" message will be sent to STDERR (
>&2). - the
cleanin the gradlew call will make sure no old artifacts are used for the build (which the first block might have missed, because e.g. your.gitignorecovers them).
Name the script e.g. mkrelease, make it executable, and put it in your $PATH. Then, whenever you want to make a release build, call this script.