Skip to content

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:

  1. Detects new releases from the GitHub repository
  2. Compares versions using versionCode from output-metadata.json
  3. Notifies users when updates are available
  4. Provides direct download links to the latest APK

How It Works ​

Version Comparison Strategy ​

The update checker uses a two-tier approach:

  1. Primary Method: Compare versionCode from output-metadata.json

    • Downloads the output-metadata.json file from the release assets
    • Parses the versionCode (integer) from the metadata
    • Compares with BuildConfig.VERSION_CODE
    • Only shows update if remote versionCode > current versionCode
  2. Fallback Method: Compare tagName (version string)

    • Used if output-metadata.json is not found in release assets
    • Used if downloading or parsing metadata fails
    • Compares release.tagName with BuildConfig.VERSION_NAME

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:

kotlin
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:

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 link

Code Implementation ​

The update check is triggered from MainActivity:

kotlin
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:

  1. Generate output-metadata.json: Automatically created by Android Gradle Plugin during build
  2. Upload to GitHub Release: Both APK and output-metadata.json must be uploaded as release assets
  3. Set version code: Use CI pipeline ID or build number as VERSION_CODE environment variable

GitLab CI Example ​

yaml
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.json

GitHub Actions Example ​

yaml
- 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/json

User 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 ​

  1. Always upload output-metadata.json: Include it as a release asset alongside the APK
  2. Use consistent version codes: Ensure version code always increases
  3. Test update flow: Verify update detection works before publishing
  4. Include APK in assets: Ensure at least one .apk file is in release assets

For Developers ​

  1. Don't modify version comparison logic: The current implementation handles edge cases
  2. Test fallback mechanism: Ensure tagName comparison works if metadata is unavailable
  3. Log debugging info: Keep Log.d() statements for troubleshooting
  4. 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.json is malformed or missing
  • Network connectivity issues
  • GitHub API rate limiting

Solution:

  • Check logs for error messages
  • Verify output-metadata.json exists 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.json is 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
  • MainActivity.kt - Update check implementation
  • data_structure/OutputMetadata.kt - Output metadata data structure
  • data_structure/GitHubRelease.kt - GitHub release API response structure
  • app/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

References ​