Skip to content

Conversation

runningcode
Copy link
Contributor

@runningcode runningcode commented Sep 17, 2025

Summary

Implements the checkForUpdateBlocking method in DistributionIntegration to check for app updates via Sentry's distribution API.

Why not reuse existing HttpConnection?

The existing HttpConnection class is designed specifically for Sentry event transport and is not suitable for distribution API calls:

  • Hardcoded for POST requests (we need GET)
  • Expects Sentry envelopes with gzip encoding (we need simple JSON)
  • Only considers status 200 successful (REST APIs use 200-299 range)
  • Includes Sentry-specific rate limiting logic

Changes

  • DistributionHttpClient: New HTTP client for distribution API requests

    • Supports GET requests with query parameters (main_binary_identifier, app_id, platform, version)
    • Uses SentryOptions.DistributionOptions for configuration (orgSlug, projectSlug, orgAuthToken)
  • UpdateResponseParser: JSON response parser for API responses

#skip-changelog

🤖 Generated with Claude Code


Note

Adds an HTTP client and JSON parser for distribution update checks, implements checkForUpdateBlocking with network/error handling, updates API types, and adds tests.

  • Android Distribution:
    • Update Check Flow: Implements DistributionIntegration.checkForUpdateBlocking using new DistributionHttpClient and UpdateResponseParser; builds params from app package info and opens download via downloadUpdate.
    • Networking & Errors: Adds detailed error handling (e.g., UnknownHostException, SocketTimeoutException) and logs; introduces UpdateStatus.NoNetwork result.
    • API Changes: UpdateInfo.getCreatedDate() becomes nullable; new UpdateStatus.NoNetwork class.
  • Testing:
    • Adds unit tests DistributionHttpClientTest (ignored real API test) and UpdateResponseParserTest (covers success, errors, edge cases).
  • Build:
    • Enables unit test Android resources/default values and adds test dependencies in build.gradle.kts.

Written by Cursor Bugbot for commit ff0acf5. This will update automatically on new commits. Configure here.

Copy link
Contributor

github-actions bot commented Sep 17, 2025

Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against ff0acf5

@runningcode runningcode force-pushed the no/implement-distribution-check-for-updates branch from 57a565d to 3e93cc4 Compare September 17, 2025 13:22
Copy link
Contributor

github-actions bot commented Sep 17, 2025

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 357.80 ms 445.41 ms 87.61 ms
Size 1.58 MiB 2.10 MiB 533.39 KiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
1df7eb6 397.04 ms 429.64 ms 32.60 ms
ee747ae 358.21 ms 389.41 ms 31.20 ms
c8125f3 383.82 ms 441.66 ms 57.84 ms
d217708 411.22 ms 430.86 ms 19.63 ms
d217708 375.27 ms 415.68 ms 40.41 ms
ee747ae 386.94 ms 431.43 ms 44.49 ms
17a0955 372.53 ms 446.70 ms 74.17 ms
f634d01 375.06 ms 420.04 ms 44.98 ms
ee747ae 396.82 ms 441.67 ms 44.86 ms
ee747ae 400.46 ms 423.61 ms 23.15 ms

App size

Revision Plain With Sentry Diff
1df7eb6 1.58 MiB 2.10 MiB 532.97 KiB
ee747ae 1.58 MiB 2.10 MiB 530.95 KiB
c8125f3 1.58 MiB 2.10 MiB 532.32 KiB
d217708 1.58 MiB 2.10 MiB 532.97 KiB
d217708 1.58 MiB 2.10 MiB 532.97 KiB
ee747ae 1.58 MiB 2.10 MiB 530.95 KiB
17a0955 1.58 MiB 2.10 MiB 533.20 KiB
f634d01 1.58 MiB 2.10 MiB 533.40 KiB
ee747ae 1.58 MiB 2.10 MiB 530.95 KiB
ee747ae 1.58 MiB 2.10 MiB 530.95 KiB

Previous results on branch: no/implement-distribution-check-for-updates

Startup times

Revision Plain With Sentry Diff
c85da5d 417.36 ms 489.36 ms 72.00 ms
6666e36 374.88 ms 444.90 ms 70.02 ms
7b367ae 349.24 ms 457.92 ms 108.68 ms
e5b2d59 363.09 ms 427.88 ms 64.79 ms

App size

Revision Plain With Sentry Diff
c85da5d 1.58 MiB 2.10 MiB 532.97 KiB
6666e36 1.58 MiB 2.10 MiB 532.97 KiB
7b367ae 1.58 MiB 2.10 MiB 533.39 KiB
e5b2d59 1.58 MiB 2.10 MiB 533.39 KiB

import javax.net.ssl.HttpsURLConnection

/** HTTP client for making requests to Sentry's distribution API. */
internal class DistributionHttpClient(private val options: SentryOptions) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had claude generate an HttpClient for me. It works. I'm just not sure if it is a good idea to develop another client.

The other alternative is to expand the existing HttpConnection to support our use case OR shadow oktthp in to our library. Open to thoughts!

import org.junit.Ignore
import org.junit.Test

class DistributionHttpClientTest {
Copy link
Contributor Author

@runningcode runningcode Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a test class I used to test the integration. In a future, SAGP will inject the authToken and other things so we won't need to specify this manually. I'll also integrate distribution in to the sample app.

@runningcode runningcode requested a review from chromy September 17, 2025 13:37
@runningcode runningcode changed the title feat(android-distribution): implement checkForUpdateBlocking functionality feat(android-distribution): add httpclient for checking for build distribution updates Sep 18, 2025
Copy link
Contributor

@chromy chromy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

UpdateStatus.UpdateError(e.message ?: "Configuration error")
} catch (e: Exception) {
sentryOptions.logger.log(SentryLevel.ERROR, e, "Failed to check for updates")
UpdateStatus.UpdateError("Network error: ${e.message}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be nice to be able to distinguish between 'not connected' and 'network error' in the return type if that is possible and easy.

Copy link
Contributor Author

@runningcode runningcode Sep 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UnknownHostException would mean there is possibly no network and SocketTimeoutException could mean a bad network. I added those to distinguish here.

@runningcode runningcode force-pushed the no/implement-distribution-check-for-updates branch from 6465b7d to 061d6b5 Compare September 22, 2025 14:20
@runningcode runningcode requested a review from chromy September 22, 2025 14:26
Copy link
Contributor

@chromy chromy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm % some small comments

cursor[bot]

This comment was marked as outdated.

@runningcode runningcode enabled auto-merge (squash) September 26, 2025 15:32
cursor[bot]

This comment was marked as outdated.

runningcode and others added 8 commits September 29, 2025 07:56
…ality

Implements the checkForUpdateBlocking method in DistributionIntegration to check for app updates via Sentry's distribution API.

## Why not reuse existing HttpConnection?

The existing `HttpConnection` class is designed specifically for Sentry event transport and is not suitable for distribution API calls:
- Hardcoded for POST requests (we need GET)
- Expects Sentry envelopes with gzip encoding (we need simple JSON)
- Only considers status 200 successful (REST APIs use 200-299 range)
- Includes Sentry-specific rate limiting logic

## Changes

- **DistributionHttpClient**: New HTTP client for distribution API requests
  - Supports GET requests with query parameters (main_binary_identifier, app_id, platform, version)
  - Uses SentryOptions.DistributionOptions for configuration (orgSlug, projectSlug, orgAuthToken)
  - Handles SSL configuration, timeouts, and proper error handling

- **UpdateResponseParser**: JSON response parser for API responses
  - Parses API responses into UpdateStatus objects (UpToDate, NewRelease, UpdateError)
  - Handles various HTTP status codes with appropriate error messages
  - Validates required fields in update information

- **DistributionIntegration**: Updated to use new classes
  - Automatically extracts app information (package name, version) from Android context
  - Clean separation of concerns with HTTP client and response parser
  - Comprehensive error handling and logging

- **Tests**: Added unit test for DistributionHttpClient with real API integration

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Remove unnecessary Claude-style comments from DistributionHttpClient
- Replace manual URL building with Android Uri.Builder for safer parameter encoding
- Add comprehensive tests for UpdateResponseParser with 11 test cases
- Improve error handling to distinguish between network connection vs server issues
- Add clarifying comments about which exceptions indicate network connectivity problems
- Fix null value handling in JSON parsing to properly validate "null" strings
- Remove unclear comment about package name usage

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Replace custom fallback "sentry-android-distribution" with error throw
when sentryClientName is null, following the pattern used throughout
the codebase where sentryClientName is expected to always be set.

Addresses PR review feedback about reusing consistent user agent.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Change "check connection speed" to "check network connection" to be
more general and align with the goal of distinguishing network
connectivity issues.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add UpdateStatus.NoNetwork subclass for network-specific errors
- Update DistributionIntegration to use NoNetwork for UnknownHostException and SocketTimeoutException
- Improve UpdateResponseParser error messages to specify which required fields are missing
- Add comprehensive tests for specific missing field error messages

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Update UpdateCheckParams constructor to use separate versionCode and versionName parameters
- Replace Android Uri with string building for better compatibility
- Remove unused Android Uri import
- Update URL construction to use build_number and build_version query parameters

This fixes the CI compilation errors where the old constructor expected a single 'version' parameter.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Extract versionCode properly from PackageInfo using longVersionCode for API 28+
- Fall back to deprecated versionCode for older Android versions
- Add missing mainBinaryIdentifier parameter to UpdateCheckParams constructor
- Use proper API-level checks to avoid deprecation warnings

This resolves the compilation error where 'version' was undefined.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Change versionCode from Int to Long in UpdateCheckParams to prevent silent
truncation of large version codes when using packageInfo.longVersionCode.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@runningcode runningcode force-pushed the no/implement-distribution-check-for-updates branch from 94d8b93 to b238c29 Compare September 29, 2025 06:02
- Add URLEncoder for proper encoding of query parameters and path segments
- Change isEmpty() to isNullOrEmpty() for null safety on configuration values
- Prevents malformed URLs with special characters and NullPointerExceptions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
)
}

return UpdateInfo(id, buildVersion, buildNumber, downloadUrl, appName, createdDate)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Empty String Passed for Nullable Field

The UpdateResponseParser passes an empty string for createdDate when it's missing from the API response. This contradicts the UpdateInfo constructor's createdDate parameter being nullable, as a missing optional field is represented by "" instead of null.

Fix in Cursor Fix in Web

@runningcode runningcode merged commit 8e6d732 into main Sep 29, 2025
62 checks passed
@runningcode runningcode deleted the no/implement-distribution-check-for-updates branch September 29, 2025 06:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants