Update Check System
Overview
The Telegram SMS app includes an automatic update checking system that uses GitHub Releases to detect and notify users of available updates. The system uses output-metadata.json for accurate version comparison based on versionCode.
Purpose
The update checking system:
- Detects new releases from the GitHub repository
- Compares versions using
versionCodefromoutput-metadata.json - Notifies users when updates are available
- Provides direct download links to the latest APK
How It Works
Version Comparison Strategy
The update checker uses a two-tier approach:
Primary Method: Compare
versionCodefromoutput-metadata.json- Downloads the
output-metadata.jsonfile from the release assets - Parses the
versionCode(integer) from the metadata - Compares with
BuildConfig.VERSION_CODE - Only shows update if remote
versionCode> currentversionCode
- Downloads the
Fallback Method: Compare
tagName(version string)- Used if
output-metadata.jsonis not found in release assets - Used if downloading or parsing metadata fails
- Compares
release.tagNamewithBuildConfig.VERSION_NAME
- Used if
Why versionCode?
Using versionCode for comparison is more reliable than version strings because:
- Integer comparison: Simple numeric comparison (e.g., 2026 > 2025)
- No parsing errors: Avoids issues with version string formats
- Always incremental: Version codes always increase with each build
- CI/CD friendly: Automatically generated from CI pipeline ID
Technical Details
Data Structures
OutputMetadata
The output-metadata.json file structure:
data class OutputMetadata(
val version: Int, // Metadata format version
val artifactType: ArtifactType, // Build artifact type
val applicationId: String, // Package name
val variantName: String, // Build variant (release/debug)
val elements: List<Element> // Output elements
)
data class Element(
val type: String, // Output type (e.g., "SINGLE")
val versionCode: Int, // App version code
val versionName: String, // App version name
val enabled: Boolean, // Whether this output is enabled
val outputFile: String // APK filename
)Example output-metadata.json:
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.qwe7002.telegram_sms",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"versionCode": 2026,
"versionName": "26.01",
"enabled": true,
"outputFile": "app-release.apk"
}
]
}Update Check Flow
User triggers check
↓
Fetch latest release from GitHub API
↓
Parse GitHubRelease JSON
↓
Search for output-metadata.json in assets
↓
Found? ──No──→ Fallback to tagName comparison
↓ Yes
Download output-metadata.json
↓
Parse OutputMetadata
↓
Compare versionCode: remote > current?
↓ Yes
Find APK asset in release
↓
Show update dialog with download linkCode Implementation
The update check is triggered from MainActivity:
private fun checkUpdate() {
// Check for debug builds
if (BuildConfig.DEBUG) {
showErrorDialog("Debug version can not check update.")
return
}
// Determine app identifier (main or nightly)
var appIdentifier = applicationContext.getString(R.string.app_identifier)
if(BuildConfig.VERSION_NAME.contains("nightly")){
appIdentifier += "-nightly"
}
// Fetch latest release from GitHub
val requestUri = String.format(
"https://api.github.com/repos/telegram-sms/%s/releases/latest",
appIdentifier
)
// ... OkHttp request handling ...
// Look for output-metadata.json in release assets
val metadataAsset = release.assets.find { it.name == "output-metadata.json" }
if (metadataAsset != null) {
checkVersionFromMetadata(release, metadataAsset.browserDownloadUrl, okhttpObj)
} else {
// Fallback to tagName comparison
}
}
private fun checkVersionFromMetadata(
release: GitHubRelease,
metadataUrl: String,
okhttpObj: OkHttpClient
) {
// Download metadata
// Parse JSON
val metadata = gson.fromJson(metadataJson, OutputMetadata::class.java)
// Compare versionCode
val remoteVersionCode = metadata.elements.firstOrNull()?.versionCode
if (remoteVersionCode != null && remoteVersionCode > BuildConfig.VERSION_CODE) {
// Show update dialog
}
}Repository Support
The update checker works with both main and nightly repositories:
Main Repository:
telegram-sms/telegram-sms- For stable releases
- Version format:
YY.MM(e.g., "26.01")
Nightly Repository:
telegram-sms/telegram-sms-nightly- For pre-release builds
- Version format:
YYYYMMDDHHMM-branch-sha - Automatically detected if
BuildConfig.VERSION_NAME.contains("nightly")
CI/CD Integration
The update checking system relies on CI/CD pipelines to:
- Generate output-metadata.json: Automatically created by Android Gradle Plugin during build
- Upload to GitHub Release: Both APK and output-metadata.json must be uploaded as release assets
- Set version code: Use CI pipeline ID or build number as
VERSION_CODEenvironment variable
GitLab CI Example
build_release:
stage: build
script:
- export VERSION_CODE=$CI_PIPELINE_ID
- export VERSION_NAME="26.01"
- ./gradlew assembleRelease
- cp app/build/outputs/apk/release/app-release.apk ./
- cp app/build/outputs/apk/release/output-metadata.json ./
artifacts:
paths:
- app-release.apk
- output-metadata.jsonGitHub Actions Example
- name: Build Release APK
run: |
export VERSION_CODE=${{ github.run_number }}
export VERSION_NAME="26.01"
./gradlew assembleRelease
- name: Upload Release Assets
uses: actions/upload-release-asset@v1
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./app/build/outputs/apk/release/app-release.apk
asset_name: app-release.apk
asset_content_type: application/vnd.android.package-archive
- name: Upload Metadata
uses: actions/upload-release-asset@v1
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./app/build/outputs/apk/release/output-metadata.json
asset_name: output-metadata.json
asset_content_type: application/jsonUser Experience
Update Check Trigger
Users can manually trigger update check from:
- Main Menu → Check for Updates
Update Dialog
When an update is available, users see:
- Version information: New version number
- Download button: Direct link to download APK
- Changelog: Release notes (if available)
Error Handling
The system handles various error scenarios:
- Network errors: Shows error message to user
- Missing metadata: Falls back to tagName comparison
- Parse errors: Falls back to tagName comparison
- Debug builds: Prevents update check (debug builds should not be updated)
Best Practices
For Release Maintainers
- Always upload output-metadata.json: Include it as a release asset alongside the APK
- Use consistent version codes: Ensure version code always increases
- Test update flow: Verify update detection works before publishing
- Include APK in assets: Ensure at least one
.apkfile is in release assets
For Developers
- Don't modify version comparison logic: The current implementation handles edge cases
- Test fallback mechanism: Ensure tagName comparison works if metadata is unavailable
- Log debugging info: Keep
Log.d()statements for troubleshooting - Handle all exceptions: Don't let parsing errors crash the app
Troubleshooting
Update not detected
Possible causes:
- Remote version code is not greater than current version code
output-metadata.jsonis malformed or missing- Network connectivity issues
- GitHub API rate limiting
Solution:
- Check logs for error messages
- Verify
output-metadata.jsonexists in release assets - Ensure version code in metadata is correct
- Wait and retry if rate limited
False positive update detection
Possible causes:
- Using tagName comparison (fallback mode)
- Version string doesn't match exactly
Solution:
- Ensure
output-metadata.jsonis uploaded to release - Verify metadata contains correct version code
- Check that version code increases with each build
Debug build trying to update
Expected behavior:
- Debug builds show error: "Debug version can not check update."
- This is intentional to prevent accidental updates of debug builds
Related Files
MainActivity.kt- Update check implementationdata_structure/OutputMetadata.kt- Output metadata data structuredata_structure/GitHubRelease.kt- GitHub release API response structureapp/build.gradle- Version code configuration
Future Improvements
Potential enhancements to consider:
- Automatic updates: Download and install updates automatically (with user permission)
- Update channels: Stable, beta, nightly channels
- Delta updates: Download only changed files
- In-app changelog: Display release notes in update dialog
- Background checks: Periodic automatic update checks
- Update preferences: User-configurable update check frequency