Android Signing Setup

jjoonleo edited this page Jun 26, 2026 · 8 revisions

Android Signing Setup

Use this guide when preparing OnTime for Google Play release builds.

Key Concepts

  • Google Play releases should use Play App Signing.
  • The local team keeps an upload key and uses it to sign app bundles before uploading them to Play Console.
  • Google Play uses the upload key to verify the upload, then signs distributed APKs with the app signing key.
  • Do not commit keystores, passwords, android/key.properties, or generated release artifacts.

Ownership and Secret Storage

The Android release owner for club.devkor.ontime owns the upload keystore process. This role is responsible for:

  • creating or confirming the current upload keystore;
  • storing the keystore file and passwords in the team password manager or CI secret manager;
  • limiting access to release maintainers;
  • documenting recovery notes in the secure storage record;
  • rotating or requesting a Play Console upload-key reset if the upload key is lost or exposed.

Never store keystore files or passwords in git, issue comments, pull requests, chat logs, screenshots, or build artifacts.

Create an Upload Key

Create and store the keystore somewhere outside the repository:

mkdir -p ~/secure
keytool -genkeypair -v \
-keystore ~/secure/ontime-upload.jks \
-storetype JKS \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-alias ontime

Save the keystore file and passwords in the team password manager or secret manager. Losing the upload key requires a Play Console upload-key reset.

Configure Local Release Signing

Create android/key.properties locally:

storeFile=/absolute/path/to/ontime-upload.jks
storePassword=<keystore-password>
keyAlias=ontime
keyPassword=<key-password>

android/key.properties is ignored by git and must stay local.

Alternatively, set environment variables:

export ANDROID_KEYSTORE_PATH=/absolute/path/to/ontime-upload.jks
export ANDROID_KEYSTORE_PASSWORD='<keystore-password>'export ANDROID_KEY_ALIAS=ontime
export ANDROID_KEY_PASSWORD='<key-password>'

The Gradle build also accepts the legacy ONTIME_ANDROID_KEYSTORE_PATH, ONTIME_ANDROID_KEYSTORE_PASSWORD, ONTIME_ANDROID_KEY_ALIAS, and ONTIME_ANDROID_KEY_PASSWORD variable names for compatibility.

Configure CI Release Signing

For GitHub Actions deploys, base64-encode the upload keystore and store it as the ANDROID_UPLOAD_KEYSTORE_B64 secret in the protected staging environment used by the Android Play Internal Deploy workflow:

base64 -i ~/secure/ontime-upload.jks | pbcopy

On Linux, use:

base64 -w 0 ~/secure/ontime-upload.jks

The deploy workflow decodes this secret into $RUNNER_TEMP/ontime-upload.jks and exports ANDROID_KEYSTORE_PATH to that temporary file. Do not create android/key.properties in CI.

The protected environment must also provide:

  • ANDROID_KEYSTORE_PASSWORD
  • ANDROID_KEY_ALIAS
  • ANDROID_KEY_PASSWORD

Build a Signed Release

Google Play prefers Android App Bundles:

flutter build appbundle --release \
--build-name=<version name from pubspec.yaml> \
--build-number=<monotonic Android versionCode>

For APK validation outside Play:

flutter build apk --release

The Gradle config intentionally fails release builds when signing secrets are missing. The failure lists missing local or CI inputs, including storeFile/ANDROID_KEYSTORE_PATH, storePassword/ANDROID_KEYSTORE_PASSWORD, keyAlias/ANDROID_KEY_ALIAS, and keyPassword/ANDROID_KEY_PASSWORD. Debug builds do not require release signing secrets.

First Play Console Release

  1. Create or open the app in Google Play Console.
  2. Create a release on an internal testing track first.
  3. Configure Play App Signing when prompted.
  4. Use Google-generated app signing key unless the team needs to share the same signing key across multiple stores or related apps.
  5. Upload the .aab from build/app/outputs/bundle/release/.
  6. After Play App Signing is active, continue signing future uploads with the same local upload key.
  7. Record the Play app signing and upload key SHA-1/SHA-256 fingerprints using docs/Android-Play-Signing-Fingerprints.md.

Existing Play Console App

  • If the app already has an upload key, use that existing key.
  • If the upload key is lost but Play App Signing is enabled, request an upload key reset in Play Console.
  • If Play App Signing is not enabled and the app signing key is lost, the app generally cannot be updated under the same package name.

Verification

Run these checks before handing a release build to QA or Play Console:

flutter analyze
flutter test
flutter build appbundle --release \
--build-name=<version name from pubspec.yaml> \
--build-number=<monotonic Android versionCode>

Confirm that pubspec.yaml has the intended public version name. In GitHub Actions deploys, Android versionCode comes from the manual android_version_code workflow input. Provide a build number greater than every previous Play Console upload for club.devkor.ontime.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Android Signing Setup

jjoonleo edited this page Jun 26, 2026 · 8 revisions

Android Signing Setup

Use this guide when preparing OnTime for Google Play release builds.

Key Concepts

  • Google Play releases should use Play App Signing.
  • The local team keeps an upload key and uses it to sign app bundles before uploading them to Play Console.
  • Google Play uses the upload key to verify the upload, then signs distributed APKs with the app signing key.
  • Do not commit keystores, passwords, android/key.properties, or generated release artifacts.

Ownership and Secret Storage

The Android release owner for club.devkor.ontime owns the upload keystore process. This role is responsible for:

  • creating or confirming the current upload keystore;
  • storing the keystore file and passwords in the team password manager or CI secret manager;
  • limiting access to release maintainers;
  • documenting recovery notes in the secure storage record;
  • rotating or requesting a Play Console upload-key reset if the upload key is lost or exposed.

Never store keystore files or passwords in git, issue comments, pull requests, chat logs, screenshots, or build artifacts.

Create an Upload Key

Create and store the keystore somewhere outside the repository:

mkdir -p ~/secure
keytool -genkeypair -v \
-keystore ~/secure/ontime-upload.jks \
-storetype JKS \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-alias ontime

Save the keystore file and passwords in the team password manager or secret manager. Losing the upload key requires a Play Console upload-key reset.

Configure Local Release Signing

Create android/key.properties locally:

storeFile=/absolute/path/to/ontime-upload.jks
storePassword=<keystore-password>
keyAlias=ontime
keyPassword=<key-password>

android/key.properties is ignored by git and must stay local.

Alternatively, set environment variables:

export ANDROID_KEYSTORE_PATH=/absolute/path/to/ontime-upload.jks
export ANDROID_KEYSTORE_PASSWORD='<keystore-password>'export ANDROID_KEY_ALIAS=ontime
export ANDROID_KEY_PASSWORD='<key-password>'

The Gradle build also accepts the legacy ONTIME_ANDROID_KEYSTORE_PATH, ONTIME_ANDROID_KEYSTORE_PASSWORD, ONTIME_ANDROID_KEY_ALIAS, and ONTIME_ANDROID_KEY_PASSWORD variable names for compatibility.

Configure CI Release Signing

For GitHub Actions deploys, base64-encode the upload keystore and store it as the ANDROID_UPLOAD_KEYSTORE_B64 secret in the protected staging environment used by the Android Play Internal Deploy workflow:

base64 -i ~/secure/ontime-upload.jks | pbcopy

On Linux, use:

base64 -w 0 ~/secure/ontime-upload.jks

The deploy workflow decodes this secret into $RUNNER_TEMP/ontime-upload.jks and exports ANDROID_KEYSTORE_PATH to that temporary file. Do not create android/key.properties in CI.

The protected environment must also provide:

  • ANDROID_KEYSTORE_PASSWORD
  • ANDROID_KEY_ALIAS
  • ANDROID_KEY_PASSWORD

Build a Signed Release

Google Play prefers Android App Bundles:

flutter build appbundle --release \
--build-name=<version name from pubspec.yaml> \
--build-number=<monotonic Android versionCode>

For APK validation outside Play:

flutter build apk --release

The Gradle config intentionally fails release builds when signing secrets are missing. The failure lists missing local or CI inputs, including storeFile/ANDROID_KEYSTORE_PATH, storePassword/ANDROID_KEYSTORE_PASSWORD, keyAlias/ANDROID_KEY_ALIAS, and keyPassword/ANDROID_KEY_PASSWORD. Debug builds do not require release signing secrets.

First Play Console Release

  1. Create or open the app in Google Play Console.
  2. Create a release on an internal testing track first.
  3. Configure Play App Signing when prompted.
  4. Use Google-generated app signing key unless the team needs to share the same signing key across multiple stores or related apps.
  5. Upload the .aab from build/app/outputs/bundle/release/.
  6. After Play App Signing is active, continue signing future uploads with the same local upload key.
  7. Record the Play app signing and upload key SHA-1/SHA-256 fingerprints using docs/Android-Play-Signing-Fingerprints.md.

Existing Play Console App

  • If the app already has an upload key, use that existing key.
  • If the upload key is lost but Play App Signing is enabled, request an upload key reset in Play Console.
  • If Play App Signing is not enabled and the app signing key is lost, the app generally cannot be updated under the same package name.

Verification

Run these checks before handing a release build to QA or Play Console:

flutter analyze
flutter test
flutter build appbundle --release \
--build-name=<version name from pubspec.yaml> \
--build-number=<monotonic Android versionCode>

Confirm that pubspec.yaml has the intended public version name. In GitHub Actions deploys, Android versionCode comes from the manual android_version_code workflow input. Provide a build number greater than every previous Play Console upload for club.devkor.ontime.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Android Signing Setup

jjoonleo edited this page Jun 26, 2026 · 8 revisions

Android Signing Setup

Use this guide when preparing OnTime for Google Play release builds.

Key Concepts

  • Google Play releases should use Play App Signing.
  • The local team keeps an upload key and uses it to sign app bundles before uploading them to Play Console.
  • Google Play uses the upload key to verify the upload, then signs distributed APKs with the app signing key.
  • Do not commit keystores, passwords, android/key.properties, or generated release artifacts.

Ownership and Secret Storage

The Android release owner for club.devkor.ontime owns the upload keystore process. This role is responsible for:

  • creating or confirming the current upload keystore;
  • storing the keystore file and passwords in the team password manager or CI secret manager;
  • limiting access to release maintainers;
  • documenting recovery notes in the secure storage record;
  • rotating or requesting a Play Console upload-key reset if the upload key is lost or exposed.

Never store keystore files or passwords in git, issue comments, pull requests, chat logs, screenshots, or build artifacts.

Create an Upload Key

Create and store the keystore somewhere outside the repository:

mkdir -p ~/secure
keytool -genkeypair -v \
-keystore ~/secure/ontime-upload.jks \
-storetype JKS \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-alias ontime

Save the keystore file and passwords in the team password manager or secret manager. Losing the upload key requires a Play Console upload-key reset.

Configure Local Release Signing

Create android/key.properties locally:

storeFile=/absolute/path/to/ontime-upload.jks
storePassword=<keystore-password>
keyAlias=ontime
keyPassword=<key-password>

android/key.properties is ignored by git and must stay local.

Alternatively, set environment variables:

export ANDROID_KEYSTORE_PATH=/absolute/path/to/ontime-upload.jks
export ANDROID_KEYSTORE_PASSWORD='<keystore-password>'export ANDROID_KEY_ALIAS=ontime
export ANDROID_KEY_PASSWORD='<key-password>'

The Gradle build also accepts the legacy ONTIME_ANDROID_KEYSTORE_PATH, ONTIME_ANDROID_KEYSTORE_PASSWORD, ONTIME_ANDROID_KEY_ALIAS, and ONTIME_ANDROID_KEY_PASSWORD variable names for compatibility.

Configure CI Release Signing

For GitHub Actions deploys, base64-encode the upload keystore and store it as the ANDROID_UPLOAD_KEYSTORE_B64 secret in the protected staging environment used by the Android Play Internal Deploy workflow:

base64 -i ~/secure/ontime-upload.jks | pbcopy

On Linux, use:

base64 -w 0 ~/secure/ontime-upload.jks

The deploy workflow decodes this secret into $RUNNER_TEMP/ontime-upload.jks and exports ANDROID_KEYSTORE_PATH to that temporary file. Do not create android/key.properties in CI.

The protected environment must also provide:

  • ANDROID_KEYSTORE_PASSWORD
  • ANDROID_KEY_ALIAS
  • ANDROID_KEY_PASSWORD

Build a Signed Release

Google Play prefers Android App Bundles:

flutter build appbundle --release \
--build-name=<version name from pubspec.yaml> \
--build-number=<monotonic Android versionCode>

For APK validation outside Play:

flutter build apk --release

The Gradle config intentionally fails release builds when signing secrets are missing. The failure lists missing local or CI inputs, including storeFile/ANDROID_KEYSTORE_PATH, storePassword/ANDROID_KEYSTORE_PASSWORD, keyAlias/ANDROID_KEY_ALIAS, and keyPassword/ANDROID_KEY_PASSWORD. Debug builds do not require release signing secrets.

First Play Console Release

  1. Create or open the app in Google Play Console.
  2. Create a release on an internal testing track first.
  3. Configure Play App Signing when prompted.
  4. Use Google-generated app signing key unless the team needs to share the same signing key across multiple stores or related apps.
  5. Upload the .aab from build/app/outputs/bundle/release/.
  6. After Play App Signing is active, continue signing future uploads with the same local upload key.
  7. Record the Play app signing and upload key SHA-1/SHA-256 fingerprints using docs/Android-Play-Signing-Fingerprints.md.

Existing Play Console App

  • If the app already has an upload key, use that existing key.
  • If the upload key is lost but Play App Signing is enabled, request an upload key reset in Play Console.
  • If Play App Signing is not enabled and the app signing key is lost, the app generally cannot be updated under the same package name.

Verification

Run these checks before handing a release build to QA or Play Console:

flutter analyze
flutter test
flutter build appbundle --release \
--build-name=<version name from pubspec.yaml> \
--build-number=<monotonic Android versionCode>

Confirm that pubspec.yaml has the intended public version name. In GitHub Actions deploys, Android versionCode comes from the manual android_version_code workflow input. Provide a build number greater than every previous Play Console upload for club.devkor.ontime.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Android Signing Setup

jjoonleo edited this page Jun 26, 2026 · 8 revisions

Android Signing Setup

Use this guide when preparing OnTime for Google Play release builds.

Key Concepts

  • Google Play releases should use Play App Signing.
  • The local team keeps an upload key and uses it to sign app bundles before uploading them to Play Console.
  • Google Play uses the upload key to verify the upload, then signs distributed APKs with the app signing key.
  • Do not commit keystores, passwords, android/key.properties, or generated release artifacts.

Ownership and Secret Storage

The Android release owner for club.devkor.ontime owns the upload keystore process. This role is responsible for:

  • creating or confirming the current upload keystore;
  • storing the keystore file and passwords in the team password manager or CI secret manager;
  • limiting access to release maintainers;
  • documenting recovery notes in the secure storage record;
  • rotating or requesting a Play Console upload-key reset if the upload key is lost or exposed.

Never store keystore files or passwords in git, issue comments, pull requests, chat logs, screenshots, or build artifacts.

Create an Upload Key

Create and store the keystore somewhere outside the repository:

mkdir -p ~/secure
keytool -genkeypair -v \
-keystore ~/secure/ontime-upload.jks \
-storetype JKS \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-alias ontime

Save the keystore file and passwords in the team password manager or secret manager. Losing the upload key requires a Play Console upload-key reset.

Configure Local Release Signing

Create android/key.properties locally:

storeFile=/absolute/path/to/ontime-upload.jks
storePassword=<keystore-password>
keyAlias=ontime
keyPassword=<key-password>

android/key.properties is ignored by git and must stay local.

Alternatively, set environment variables:

export ANDROID_KEYSTORE_PATH=/absolute/path/to/ontime-upload.jks
export ANDROID_KEYSTORE_PASSWORD='<keystore-password>'export ANDROID_KEY_ALIAS=ontime
export ANDROID_KEY_PASSWORD='<key-password>'

The Gradle build also accepts the legacy ONTIME_ANDROID_KEYSTORE_PATH, ONTIME_ANDROID_KEYSTORE_PASSWORD, ONTIME_ANDROID_KEY_ALIAS, and ONTIME_ANDROID_KEY_PASSWORD variable names for compatibility.

Configure CI Release Signing

For GitHub Actions deploys, base64-encode the upload keystore and store it as the ANDROID_UPLOAD_KEYSTORE_B64 secret in the protected staging environment used by the Android Play Internal Deploy workflow:

base64 -i ~/secure/ontime-upload.jks | pbcopy

On Linux, use:

base64 -w 0 ~/secure/ontime-upload.jks

The deploy workflow decodes this secret into $RUNNER_TEMP/ontime-upload.jks and exports ANDROID_KEYSTORE_PATH to that temporary file. Do not create android/key.properties in CI.

The protected environment must also provide:

  • ANDROID_KEYSTORE_PASSWORD
  • ANDROID_KEY_ALIAS
  • ANDROID_KEY_PASSWORD

Build a Signed Release

Google Play prefers Android App Bundles:

flutter build appbundle --release \
--build-name=<version name from pubspec.yaml> \
--build-number=<monotonic Android versionCode>

For APK validation outside Play:

flutter build apk --release

The Gradle config intentionally fails release builds when signing secrets are missing. The failure lists missing local or CI inputs, including storeFile/ANDROID_KEYSTORE_PATH, storePassword/ANDROID_KEYSTORE_PASSWORD, keyAlias/ANDROID_KEY_ALIAS, and keyPassword/ANDROID_KEY_PASSWORD. Debug builds do not require release signing secrets.

First Play Console Release

  1. Create or open the app in Google Play Console.
  2. Create a release on an internal testing track first.
  3. Configure Play App Signing when prompted.
  4. Use Google-generated app signing key unless the team needs to share the same signing key across multiple stores or related apps.
  5. Upload the .aab from build/app/outputs/bundle/release/.
  6. After Play App Signing is active, continue signing future uploads with the same local upload key.
  7. Record the Play app signing and upload key SHA-1/SHA-256 fingerprints using docs/Android-Play-Signing-Fingerprints.md.

Existing Play Console App

  • If the app already has an upload key, use that existing key.
  • If the upload key is lost but Play App Signing is enabled, request an upload key reset in Play Console.
  • If Play App Signing is not enabled and the app signing key is lost, the app generally cannot be updated under the same package name.

Verification

Run these checks before handing a release build to QA or Play Console:

flutter analyze
flutter test
flutter build appbundle --release \
--build-name=<version name from pubspec.yaml> \
--build-number=<monotonic Android versionCode>

Confirm that pubspec.yaml has the intended public version name. In GitHub Actions deploys, Android versionCode comes from the manual android_version_code workflow input. Provide a build number greater than every previous Play Console upload for club.devkor.ontime.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Android Signing Setup

jjoonleo edited this page Jun 26, 2026 · 8 revisions

Android Signing Setup

Use this guide when preparing OnTime for Google Play release builds.

Key Concepts

  • Google Play releases should use Play App Signing.
  • The local team keeps an upload key and uses it to sign app bundles before uploading them to Play Console.
  • Google Play uses the upload key to verify the upload, then signs distributed APKs with the app signing key.
  • Do not commit keystores, passwords, android/key.properties, or generated release artifacts.

Ownership and Secret Storage

The Android release owner for club.devkor.ontime owns the upload keystore process. This role is responsible for:

  • creating or confirming the current upload keystore;
  • storing the keystore file and passwords in the team password manager or CI secret manager;
  • limiting access to release maintainers;
  • documenting recovery notes in the secure storage record;
  • rotating or requesting a Play Console upload-key reset if the upload key is lost or exposed.

Never store keystore files or passwords in git, issue comments, pull requests, chat logs, screenshots, or build artifacts.

Create an Upload Key

Create and store the keystore somewhere outside the repository:

mkdir -p ~/secure
keytool -genkeypair -v \
-keystore ~/secure/ontime-upload.jks \
-storetype JKS \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-alias ontime

Save the keystore file and passwords in the team password manager or secret manager. Losing the upload key requires a Play Console upload-key reset.

Configure Local Release Signing

Create android/key.properties locally:

storeFile=/absolute/path/to/ontime-upload.jks
storePassword=<keystore-password>
keyAlias=ontime
keyPassword=<key-password>

android/key.properties is ignored by git and must stay local.

Alternatively, set environment variables:

export ANDROID_KEYSTORE_PATH=/absolute/path/to/ontime-upload.jks
export ANDROID_KEYSTORE_PASSWORD='<keystore-password>'export ANDROID_KEY_ALIAS=ontime
export ANDROID_KEY_PASSWORD='<key-password>'

The Gradle build also accepts the legacy ONTIME_ANDROID_KEYSTORE_PATH, ONTIME_ANDROID_KEYSTORE_PASSWORD, ONTIME_ANDROID_KEY_ALIAS, and ONTIME_ANDROID_KEY_PASSWORD variable names for compatibility.

Configure CI Release Signing

For GitHub Actions deploys, base64-encode the upload keystore and store it as the ANDROID_UPLOAD_KEYSTORE_B64 secret in the protected staging environment used by the Android Play Internal Deploy workflow:

base64 -i ~/secure/ontime-upload.jks | pbcopy

On Linux, use:

base64 -w 0 ~/secure/ontime-upload.jks

The deploy workflow decodes this secret into $RUNNER_TEMP/ontime-upload.jks and exports ANDROID_KEYSTORE_PATH to that temporary file. Do not create android/key.properties in CI.

The protected environment must also provide:

  • ANDROID_KEYSTORE_PASSWORD
  • ANDROID_KEY_ALIAS
  • ANDROID_KEY_PASSWORD

Build a Signed Release

Google Play prefers Android App Bundles:

flutter build appbundle --release \
--build-name=<version name from pubspec.yaml> \
--build-number=<monotonic Android versionCode>

For APK validation outside Play:

flutter build apk --release

The Gradle config intentionally fails release builds when signing secrets are missing. The failure lists missing local or CI inputs, including storeFile/ANDROID_KEYSTORE_PATH, storePassword/ANDROID_KEYSTORE_PASSWORD, keyAlias/ANDROID_KEY_ALIAS, and keyPassword/ANDROID_KEY_PASSWORD. Debug builds do not require release signing secrets.

First Play Console Release

  1. Create or open the app in Google Play Console.
  2. Create a release on an internal testing track first.
  3. Configure Play App Signing when prompted.
  4. Use Google-generated app signing key unless the team needs to share the same signing key across multiple stores or related apps.
  5. Upload the .aab from build/app/outputs/bundle/release/.
  6. After Play App Signing is active, continue signing future uploads with the same local upload key.
  7. Record the Play app signing and upload key SHA-1/SHA-256 fingerprints using docs/Android-Play-Signing-Fingerprints.md.

Existing Play Console App

  • If the app already has an upload key, use that existing key.
  • If the upload key is lost but Play App Signing is enabled, request an upload key reset in Play Console.
  • If Play App Signing is not enabled and the app signing key is lost, the app generally cannot be updated under the same package name.

Verification

Run these checks before handing a release build to QA or Play Console:

flutter analyze
flutter test
flutter build appbundle --release \
--build-name=<version name from pubspec.yaml> \
--build-number=<monotonic Android versionCode>

Confirm that pubspec.yaml has the intended public version name. In GitHub Actions deploys, Android versionCode comes from the manual android_version_code workflow input. Provide a build number greater than every previous Play Console upload for club.devkor.ontime.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Android Signing Setup

jjoonleo edited this page Jun 26, 2026 · 8 revisions

Android Signing Setup

Use this guide when preparing OnTime for Google Play release builds.

Key Concepts

  • Google Play releases should use Play App Signing.
  • The local team keeps an upload key and uses it to sign app bundles before uploading them to Play Console.
  • Google Play uses the upload key to verify the upload, then signs distributed APKs with the app signing key.
  • Do not commit keystores, passwords, android/key.properties, or generated release artifacts.

Ownership and Secret Storage

The Android release owner for club.devkor.ontime owns the upload keystore process. This role is responsible for:

  • creating or confirming the current upload keystore;
  • storing the keystore file and passwords in the team password manager or CI secret manager;
  • limiting access to release maintainers;
  • documenting recovery notes in the secure storage record;
  • rotating or requesting a Play Console upload-key reset if the upload key is lost or exposed.

Never store keystore files or passwords in git, issue comments, pull requests, chat logs, screenshots, or build artifacts.

Create an Upload Key

Create and store the keystore somewhere outside the repository:

mkdir -p ~/secure
keytool -genkeypair -v \
-keystore ~/secure/ontime-upload.jks \
-storetype JKS \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-alias ontime

Save the keystore file and passwords in the team password manager or secret manager. Losing the upload key requires a Play Console upload-key reset.

Configure Local Release Signing

Create android/key.properties locally:

storeFile=/absolute/path/to/ontime-upload.jks
storePassword=<keystore-password>
keyAlias=ontime
keyPassword=<key-password>

android/key.properties is ignored by git and must stay local.

Alternatively, set environment variables:

export ANDROID_KEYSTORE_PATH=/absolute/path/to/ontime-upload.jks
export ANDROID_KEYSTORE_PASSWORD='<keystore-password>'export ANDROID_KEY_ALIAS=ontime
export ANDROID_KEY_PASSWORD='<key-password>'

The Gradle build also accepts the legacy ONTIME_ANDROID_KEYSTORE_PATH, ONTIME_ANDROID_KEYSTORE_PASSWORD, ONTIME_ANDROID_KEY_ALIAS, and ONTIME_ANDROID_KEY_PASSWORD variable names for compatibility.

Configure CI Release Signing

For GitHub Actions deploys, base64-encode the upload keystore and store it as the ANDROID_UPLOAD_KEYSTORE_B64 secret in the protected staging environment used by the Android Play Internal Deploy workflow:

base64 -i ~/secure/ontime-upload.jks | pbcopy

On Linux, use:

base64 -w 0 ~/secure/ontime-upload.jks

The deploy workflow decodes this secret into $RUNNER_TEMP/ontime-upload.jks and exports ANDROID_KEYSTORE_PATH to that temporary file. Do not create android/key.properties in CI.

The protected environment must also provide:

  • ANDROID_KEYSTORE_PASSWORD
  • ANDROID_KEY_ALIAS
  • ANDROID_KEY_PASSWORD

Build a Signed Release

Google Play prefers Android App Bundles:

flutter build appbundle --release \
--build-name=<version name from pubspec.yaml> \
--build-number=<monotonic Android versionCode>

For APK validation outside Play:

flutter build apk --release

The Gradle config intentionally fails release builds when signing secrets are missing. The failure lists missing local or CI inputs, including storeFile/ANDROID_KEYSTORE_PATH, storePassword/ANDROID_KEYSTORE_PASSWORD, keyAlias/ANDROID_KEY_ALIAS, and keyPassword/ANDROID_KEY_PASSWORD. Debug builds do not require release signing secrets.

First Play Console Release

  1. Create or open the app in Google Play Console.
  2. Create a release on an internal testing track first.
  3. Configure Play App Signing when prompted.
  4. Use Google-generated app signing key unless the team needs to share the same signing key across multiple stores or related apps.
  5. Upload the .aab from build/app/outputs/bundle/release/.
  6. After Play App Signing is active, continue signing future uploads with the same local upload key.
  7. Record the Play app signing and upload key SHA-1/SHA-256 fingerprints using docs/Android-Play-Signing-Fingerprints.md.

Existing Play Console App

  • If the app already has an upload key, use that existing key.
  • If the upload key is lost but Play App Signing is enabled, request an upload key reset in Play Console.
  • If Play App Signing is not enabled and the app signing key is lost, the app generally cannot be updated under the same package name.

Verification

Run these checks before handing a release build to QA or Play Console:

flutter analyze
flutter test
flutter build appbundle --release \
--build-name=<version name from pubspec.yaml> \
--build-number=<monotonic Android versionCode>

Confirm that pubspec.yaml has the intended public version name. In GitHub Actions deploys, Android versionCode comes from the manual android_version_code workflow input. Provide a build number greater than every previous Play Console upload for club.devkor.ontime.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Android Signing Setup

jjoonleo edited this page Jun 26, 2026 · 8 revisions

Android Signing Setup

Use this guide when preparing OnTime for Google Play release builds.

Key Concepts

  • Google Play releases should use Play App Signing.
  • The local team keeps an upload key and uses it to sign app bundles before uploading them to Play Console.
  • Google Play uses the upload key to verify the upload, then signs distributed APKs with the app signing key.
  • Do not commit keystores, passwords, android/key.properties, or generated release artifacts.

Ownership and Secret Storage

The Android release owner for club.devkor.ontime owns the upload keystore process. This role is responsible for:

  • creating or confirming the current upload keystore;
  • storing the keystore file and passwords in the team password manager or CI secret manager;
  • limiting access to release maintainers;
  • documenting recovery notes in the secure storage record;
  • rotating or requesting a Play Console upload-key reset if the upload key is lost or exposed.

Never store keystore files or passwords in git, issue comments, pull requests, chat logs, screenshots, or build artifacts.

Create an Upload Key

Create and store the keystore somewhere outside the repository:

mkdir -p ~/secure
keytool -genkeypair -v \
-keystore ~/secure/ontime-upload.jks \
-storetype JKS \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-alias ontime

Save the keystore file and passwords in the team password manager or secret manager. Losing the upload key requires a Play Console upload-key reset.

Configure Local Release Signing

Create android/key.properties locally:

storeFile=/absolute/path/to/ontime-upload.jks
storePassword=<keystore-password>
keyAlias=ontime
keyPassword=<key-password>

android/key.properties is ignored by git and must stay local.

Alternatively, set environment variables:

export ANDROID_KEYSTORE_PATH=/absolute/path/to/ontime-upload.jks
export ANDROID_KEYSTORE_PASSWORD='<keystore-password>'export ANDROID_KEY_ALIAS=ontime
export ANDROID_KEY_PASSWORD='<key-password>'

The Gradle build also accepts the legacy ONTIME_ANDROID_KEYSTORE_PATH, ONTIME_ANDROID_KEYSTORE_PASSWORD, ONTIME_ANDROID_KEY_ALIAS, and ONTIME_ANDROID_KEY_PASSWORD variable names for compatibility.

Configure CI Release Signing

For GitHub Actions deploys, base64-encode the upload keystore and store it as the ANDROID_UPLOAD_KEYSTORE_B64 secret in the protected staging environment used by the Android Play Internal Deploy workflow:

base64 -i ~/secure/ontime-upload.jks | pbcopy

On Linux, use:

base64 -w 0 ~/secure/ontime-upload.jks

The deploy workflow decodes this secret into $RUNNER_TEMP/ontime-upload.jks and exports ANDROID_KEYSTORE_PATH to that temporary file. Do not create android/key.properties in CI.

The protected environment must also provide:

  • ANDROID_KEYSTORE_PASSWORD
  • ANDROID_KEY_ALIAS
  • ANDROID_KEY_PASSWORD

Build a Signed Release

Google Play prefers Android App Bundles:

flutter build appbundle --release \
--build-name=<version name from pubspec.yaml> \
--build-number=<monotonic Android versionCode>

For APK validation outside Play:

flutter build apk --release

The Gradle config intentionally fails release builds when signing secrets are missing. The failure lists missing local or CI inputs, including storeFile/ANDROID_KEYSTORE_PATH, storePassword/ANDROID_KEYSTORE_PASSWORD, keyAlias/ANDROID_KEY_ALIAS, and keyPassword/ANDROID_KEY_PASSWORD. Debug builds do not require release signing secrets.

First Play Console Release

  1. Create or open the app in Google Play Console.
  2. Create a release on an internal testing track first.
  3. Configure Play App Signing when prompted.
  4. Use Google-generated app signing key unless the team needs to share the same signing key across multiple stores or related apps.
  5. Upload the .aab from build/app/outputs/bundle/release/.
  6. After Play App Signing is active, continue signing future uploads with the same local upload key.
  7. Record the Play app signing and upload key SHA-1/SHA-256 fingerprints using docs/Android-Play-Signing-Fingerprints.md.

Existing Play Console App

  • If the app already has an upload key, use that existing key.
  • If the upload key is lost but Play App Signing is enabled, request an upload key reset in Play Console.
  • If Play App Signing is not enabled and the app signing key is lost, the app generally cannot be updated under the same package name.

Verification

Run these checks before handing a release build to QA or Play Console:

flutter analyze
flutter test
flutter build appbundle --release \
--build-name=<version name from pubspec.yaml> \
--build-number=<monotonic Android versionCode>

Confirm that pubspec.yaml has the intended public version name. In GitHub Actions deploys, Android versionCode comes from the manual android_version_code workflow input. Provide a build number greater than every previous Play Console upload for club.devkor.ontime.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Android Signing Setup

jjoonleo edited this page Jun 26, 2026 · 8 revisions

Android Signing Setup

Use this guide when preparing OnTime for Google Play release builds.

Key Concepts

  • Google Play releases should use Play App Signing.
  • The local team keeps an upload key and uses it to sign app bundles before uploading them to Play Console.
  • Google Play uses the upload key to verify the upload, then signs distributed APKs with the app signing key.
  • Do not commit keystores, passwords, android/key.properties, or generated release artifacts.

Ownership and Secret Storage

The Android release owner for club.devkor.ontime owns the upload keystore process. This role is responsible for:

  • creating or confirming the current upload keystore;
  • storing the keystore file and passwords in the team password manager or CI secret manager;
  • limiting access to release maintainers;
  • documenting recovery notes in the secure storage record;
  • rotating or requesting a Play Console upload-key reset if the upload key is lost or exposed.

Never store keystore files or passwords in git, issue comments, pull requests, chat logs, screenshots, or build artifacts.

Create an Upload Key

Create and store the keystore somewhere outside the repository:

mkdir -p ~/secure
keytool -genkeypair -v \
-keystore ~/secure/ontime-upload.jks \
-storetype JKS \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-alias ontime

Save the keystore file and passwords in the team password manager or secret manager. Losing the upload key requires a Play Console upload-key reset.

Configure Local Release Signing

Create android/key.properties locally:

storeFile=/absolute/path/to/ontime-upload.jks
storePassword=<keystore-password>
keyAlias=ontime
keyPassword=<key-password>

android/key.properties is ignored by git and must stay local.

Alternatively, set environment variables:

export ANDROID_KEYSTORE_PATH=/absolute/path/to/ontime-upload.jks
export ANDROID_KEYSTORE_PASSWORD='<keystore-password>'export ANDROID_KEY_ALIAS=ontime
export ANDROID_KEY_PASSWORD='<key-password>'

The Gradle build also accepts the legacy ONTIME_ANDROID_KEYSTORE_PATH, ONTIME_ANDROID_KEYSTORE_PASSWORD, ONTIME_ANDROID_KEY_ALIAS, and ONTIME_ANDROID_KEY_PASSWORD variable names for compatibility.

Configure CI Release Signing

For GitHub Actions deploys, base64-encode the upload keystore and store it as the ANDROID_UPLOAD_KEYSTORE_B64 secret in the protected staging environment used by the Android Play Internal Deploy workflow:

base64 -i ~/secure/ontime-upload.jks | pbcopy

On Linux, use:

base64 -w 0 ~/secure/ontime-upload.jks

The deploy workflow decodes this secret into $RUNNER_TEMP/ontime-upload.jks and exports ANDROID_KEYSTORE_PATH to that temporary file. Do not create android/key.properties in CI.

The protected environment must also provide:

  • ANDROID_KEYSTORE_PASSWORD
  • ANDROID_KEY_ALIAS
  • ANDROID_KEY_PASSWORD

Build a Signed Release

Google Play prefers Android App Bundles:

flutter build appbundle --release \
--build-name=<version name from pubspec.yaml> \
--build-number=<monotonic Android versionCode>

For APK validation outside Play:

flutter build apk --release

The Gradle config intentionally fails release builds when signing secrets are missing. The failure lists missing local or CI inputs, including storeFile/ANDROID_KEYSTORE_PATH, storePassword/ANDROID_KEYSTORE_PASSWORD, keyAlias/ANDROID_KEY_ALIAS, and keyPassword/ANDROID_KEY_PASSWORD. Debug builds do not require release signing secrets.

First Play Console Release

  1. Create or open the app in Google Play Console.
  2. Create a release on an internal testing track first.
  3. Configure Play App Signing when prompted.
  4. Use Google-generated app signing key unless the team needs to share the same signing key across multiple stores or related apps.
  5. Upload the .aab from build/app/outputs/bundle/release/.
  6. After Play App Signing is active, continue signing future uploads with the same local upload key.
  7. Record the Play app signing and upload key SHA-1/SHA-256 fingerprints using docs/Android-Play-Signing-Fingerprints.md.

Existing Play Console App

  • If the app already has an upload key, use that existing key.
  • If the upload key is lost but Play App Signing is enabled, request an upload key reset in Play Console.
  • If Play App Signing is not enabled and the app signing key is lost, the app generally cannot be updated under the same package name.

Verification

Run these checks before handing a release build to QA or Play Console:

flutter analyze
flutter test
flutter build appbundle --release \
--build-name=<version name from pubspec.yaml> \
--build-number=<monotonic Android versionCode>

Confirm that pubspec.yaml has the intended public version name. In GitHub Actions deploys, Android versionCode comes from the manual android_version_code workflow input. Provide a build number greater than every previous Play Console upload for club.devkor.ontime.

Clone this wiki locally