Skip to content

Development

Project Setup

To run the project, open it in the latest Version of Android Studio and build the project as usual. The default local development variant is freeDebug, which does not require Google services credentials. Play variants include Firebase, Crashlytics, Remote Config, and Play review dependencies. Play builds require app/src/play/google-services.json; CI writes this file from the release secret.

By default, there is not enough memory configured for gradle. You can fix this by running.

scripts/gradle_bootstrap.sh

This will configure your global ~/.gradle/gradle.properties to use more memory, depending on your machine.

Example of the generated properties, check the gradle_bootstrap.sh script for exact details.

# Begin: Gradle JVM bootstrap-generated properties
org.gradle.jvmargs=-Dfile.encoding=UTF-8 -XX:+ExitOnOutOfMemoryError -Xms4g -Xmx16g
kotlin.daemon.jvm.options=-Dfile.encoding=UTF-8 -XX:+ExitOnOutOfMemoryError -Xms4g -Xmx16g
# End: Gradle JVM bootstrap-generated properties

Tests

Unit tests

To run the unit tests, run the following command:

./gradlew voiceUnitTest

Instrumentation tests

To run the instrumentation tests, run the following command:

./gradlew voiceDeviceFreeDebugAndroidTest

Developer Menu

The developer menu is hidden by default.

To unlock it:

  1. Open Settings.
  2. Tap the app version entry 13 times.
  3. Open the newly visible developer menu from Settings.

Ktlint

Voice uses Ktlint to enforce consistent code formatting.

  • Check for formatting issues:
./gradlew lintKotlin
  • Auto-fix formatting:
./gradlew formatKotlin
  • To make commits fail on formatting errors, set up a pre-commit hook:
echo "./gradlew lintKotlin" > .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit

Releasing

To release a new version, trigger the Release Workflow

This pushes a tag and publishes the release to the Play Store’s internal track. From there, promotion to production must be done manually.

The release workflow uses the protected release GitHub environment. CI signs through Gradle's injected signing properties, keeps keystores as base64 file secrets, and passes store passwords, key aliases, and key passwords as environment variables; no checked-in or generated signing properties files are required.

The release environment requires these secrets:

Secret Purpose
PLAY_SERVICE_ACCOUNT Google Play service account JSON, base64 encoded.
GOOGLE_SERVICES Play flavor google-services.json, base64 encoded.
PLAY_SIGNING_KEYSTORE Play release keystore, base64 encoded.
PLAY_SIGNING_STORE_PASSWORD Play release keystore password.
PLAY_SIGNING_KEY_ALIAS Play release key alias.
PLAY_SIGNING_KEY_PASSWORD Play release key password.
GH_SIGNING_KEYSTORE GitHub/free release keystore, base64 encoded.
GH_SIGNING_STORE_PASSWORD GitHub/free release keystore password.
GH_SIGNING_KEY_ALIAS GitHub/free release key alias.
GH_SIGNING_KEY_PASSWORD GitHub/free release key password.

F-Droid builds are handled by their team and usually appear a few days after a stable (non-RC) release.

Versioning

Voice uses calendar versioning (CalVer).

  • Version name format: YY.M.RELEASE
  • YY: release year, last 2 digits (2025 → 25)
  • M: release month (1–12)
  • RELEASE: counter for releases within the same month (starting at 1)
  • Tag format (used by CI): YY.M.RELEASE-CODE
  • CODE is the numeric Android versionCode derived from the version name using the formula below.

Examples:

  • First release in September 2025 → version name 25.9.1
  • Tag includes version code → 25.9.1-5309001

Version code

The Android versionCode is calculated from the version name with:

CODE = (YY + 28)(MM as 2 digits)(RELEASE as 3 digits)

Example: 25.9.1(25+28)=53, MM=09, RELEASE=0015309001.

This code is embedded in the git tag by release.main.kts and used by CI and Fastlane.

Pages Deployment

To projects Website uses Github Pages and Mkdocs. To deploy a new website, use dispatch a workflow manually.

👉 Dispatch Workflow