Skip to content

Commit d5a29b6

Browse files
runningcodeclaudegetsentry-bot
authored
feat(android-distribution): Run checkForUpdate on background thread (EME-413) (#4811)
* feat(android-distribution): Run checkForUpdate on background thread (EME-413) Implement async version of checkForUpdate that runs on a background thread instead of blocking the calling thread. The callback is invoked on the background thread, allowing callers to dispatch to main thread if needed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * refactor(android-distribution): Use Future instead of callback for async checkForUpdate (EME-413) Replace callback-based API with Future-based API to avoid confusion and improve consistency with SDK patterns. Use SentryExecutorService instead of spawning new threads to better manage resources. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * refactor(sentry): Use custom CompletedFuture for API 21+ compatibility (EME-413) Replace CompletableFuture.completedFuture() with a custom CompletedFuture implementation in NoOpDistributionApi to maintain Android API 21+ compatibility. CompletableFuture was only added in Android API 24. The new CompletedFuture follows the same pattern as existing CancelledFuture implementations in the codebase and returns an immediately completed Future with a result. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * refactor(android-distribution): Import Future type for cleaner code (EME-413) Add explicit import for java.util.concurrent.Future and use short form instead of fully qualified name in method signature. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * fix(samples): Update MainActivity to use Future-based checkForUpdate API (EME-413) Update sample app to use the new Future-based checkForUpdate() method instead of the old callback-based API. The Future is processed on a background thread and results are posted back to the UI thread. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * refactor(samples): Add Future import and async library comment (EME-413) Improves code readability by importing Future instead of using fully qualified name. Adds guidance comment suggesting developers convert the sample to their preferred async library (RxJava, Coroutines, etc.) in production code. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * style(samples): Reflow comment for proper line length (EME-413) Applied spotlessApply formatting to wrap long comment line. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * release: 8.24.0-alpha.2 --------- Co-authored-by: Claude <[email protected]> Co-authored-by: getsentry-bot <[email protected]>
1 parent 514df68 commit d5a29b6

File tree

8 files changed

+107
-56
lines changed

8 files changed

+107
-56
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Changelog
22

3-
## Unreleased
3+
## 8.24.0-alpha.2
44

55
### Features
66

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
1111
android.useAndroidX=true
1212

1313
# Release information
14-
versionName=8.23.0
14+
versionName=8.24.0-alpha.2
1515

1616
# Override the SDK name on native crashes on Android
1717
sentryAndroidSdkName=sentry.native.android

sentry-android-distribution/api/sentry-android-distribution.api

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
public final class io/sentry/android/distribution/DistributionIntegration : io/sentry/IDistributionApi, io/sentry/Integration {
22
public fun <init> (Landroid/content/Context;)V
3-
public fun checkForUpdate (Lio/sentry/IDistributionApi$UpdateCallback;)V
3+
public fun checkForUpdate ()Ljava/util/concurrent/Future;
44
public fun checkForUpdateBlocking ()Lio/sentry/UpdateStatus;
55
public fun downloadUpdate (Lio/sentry/UpdateInfo;)V
66
public fun isEnabled ()Z

sentry-android-distribution/src/main/java/io/sentry/android/distribution/DistributionIntegration.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import io.sentry.UpdateInfo
1414
import io.sentry.UpdateStatus
1515
import java.net.SocketTimeoutException
1616
import java.net.UnknownHostException
17+
import java.util.concurrent.Future
1718
import org.jetbrains.annotations.ApiStatus
1819

1920
/**
@@ -84,14 +85,12 @@ public class DistributionIntegration(context: Context) : Integration, IDistribut
8485
}
8586

8687
/**
87-
* Check for available updates asynchronously using a callback.
88+
* Check for available updates asynchronously.
8889
*
89-
* @param onResult Callback that will be called with the UpdateStatus result
90+
* @return Future that will resolve to an UpdateStatus result
9091
*/
91-
public override fun checkForUpdate(onResult: IDistributionApi.UpdateCallback) {
92-
// TODO implement this in a async way
93-
val result = checkForUpdateBlocking()
94-
onResult.onResult(result)
92+
public override fun checkForUpdate(): Future<UpdateStatus> {
93+
return sentryOptions.executorService.submit<UpdateStatus> { checkForUpdateBlocking() }
9594
}
9695

9796
/**

sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.Collections;
3030
import java.util.List;
3131
import java.util.concurrent.CountDownLatch;
32+
import java.util.concurrent.Future;
3233
import timber.log.Timber;
3334

3435
public class MainActivity extends AppCompatActivity {
@@ -315,35 +316,51 @@ public void run() {
315316
binding.checkForUpdate.setOnClickListener(
316317
view -> {
317318
Toast.makeText(this, "Checking for updates...", Toast.LENGTH_SHORT).show();
318-
Sentry.distribution()
319-
.checkForUpdate(
320-
result -> {
321-
runOnUiThread(
322-
() -> {
323-
String message;
324-
if (result instanceof UpdateStatus.NewRelease) {
325-
UpdateStatus.NewRelease newRelease = (UpdateStatus.NewRelease) result;
326-
message =
327-
"Update available: "
328-
+ newRelease.getInfo().getBuildVersion()
329-
+ " (Build "
330-
+ newRelease.getInfo().getBuildNumber()
331-
+ ")\nDownload URL: "
332-
+ newRelease.getInfo().getDownloadUrl();
333-
} else if (result instanceof UpdateStatus.UpToDate) {
334-
message = "App is up to date!";
335-
} else if (result instanceof UpdateStatus.NoNetwork) {
336-
UpdateStatus.NoNetwork noNetwork = (UpdateStatus.NoNetwork) result;
337-
message = "No network connection: " + noNetwork.getMessage();
338-
} else if (result instanceof UpdateStatus.UpdateError) {
339-
UpdateStatus.UpdateError error = (UpdateStatus.UpdateError) result;
340-
message = "Error checking for updates: " + error.getMessage();
341-
} else {
342-
message = "Unknown status";
343-
}
344-
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
345-
});
346-
});
319+
Future<UpdateStatus> future = Sentry.distribution().checkForUpdate();
320+
// In production, convert this to use your preferred async library (RxJava, Coroutines,
321+
// etc.)
322+
// This sample uses raw threads and Future.get() for simplicity
323+
// Process result on background thread, then update UI
324+
new Thread(
325+
() -> {
326+
try {
327+
UpdateStatus result = future.get();
328+
runOnUiThread(
329+
() -> {
330+
String message;
331+
if (result instanceof UpdateStatus.NewRelease) {
332+
UpdateStatus.NewRelease newRelease = (UpdateStatus.NewRelease) result;
333+
message =
334+
"Update available: "
335+
+ newRelease.getInfo().getBuildVersion()
336+
+ " (Build "
337+
+ newRelease.getInfo().getBuildNumber()
338+
+ ")\nDownload URL: "
339+
+ newRelease.getInfo().getDownloadUrl();
340+
} else if (result instanceof UpdateStatus.UpToDate) {
341+
message = "App is up to date!";
342+
} else if (result instanceof UpdateStatus.NoNetwork) {
343+
UpdateStatus.NoNetwork noNetwork = (UpdateStatus.NoNetwork) result;
344+
message = "No network connection: " + noNetwork.getMessage();
345+
} else if (result instanceof UpdateStatus.UpdateError) {
346+
UpdateStatus.UpdateError error = (UpdateStatus.UpdateError) result;
347+
message = "Error checking for updates: " + error.getMessage();
348+
} else {
349+
message = "Unknown status";
350+
}
351+
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
352+
});
353+
} catch (Exception e) {
354+
runOnUiThread(
355+
() ->
356+
Toast.makeText(
357+
this,
358+
"Error checking for updates: " + e.getMessage(),
359+
Toast.LENGTH_LONG)
360+
.show());
361+
}
362+
})
363+
.start();
347364
});
348365

349366
binding.openCameraActivity.setOnClickListener(

sentry/api/sentry.api

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -776,16 +776,12 @@ public abstract interface class io/sentry/IContinuousProfiler {
776776
}
777777

778778
public abstract interface class io/sentry/IDistributionApi {
779-
public abstract fun checkForUpdate (Lio/sentry/IDistributionApi$UpdateCallback;)V
779+
public abstract fun checkForUpdate ()Ljava/util/concurrent/Future;
780780
public abstract fun checkForUpdateBlocking ()Lio/sentry/UpdateStatus;
781781
public abstract fun downloadUpdate (Lio/sentry/UpdateInfo;)V
782782
public abstract fun isEnabled ()Z
783783
}
784784

785-
public abstract interface class io/sentry/IDistributionApi$UpdateCallback {
786-
public abstract fun onResult (Lio/sentry/UpdateStatus;)V
787-
}
788-
789785
public abstract interface class io/sentry/IEnvelopeReader {
790786
public abstract fun read (Ljava/io/InputStream;)Lio/sentry/SentryEnvelope;
791787
}
@@ -1494,7 +1490,7 @@ public final class io/sentry/NoOpContinuousProfiler : io/sentry/IContinuousProfi
14941490
}
14951491

14961492
public final class io/sentry/NoOpDistributionApi : io/sentry/IDistributionApi {
1497-
public fun checkForUpdate (Lio/sentry/IDistributionApi$UpdateCallback;)V
1493+
public fun checkForUpdate ()Ljava/util/concurrent/Future;
14981494
public fun checkForUpdateBlocking ()Lio/sentry/UpdateStatus;
14991495
public fun downloadUpdate (Lio/sentry/UpdateInfo;)V
15001496
public static fun getInstance ()Lio/sentry/NoOpDistributionApi;

sentry/src/main/java/io/sentry/IDistributionApi.java

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.sentry;
22

3+
import java.util.concurrent.Future;
34
import org.jetbrains.annotations.ApiStatus;
45
import org.jetbrains.annotations.NotNull;
56

@@ -14,20 +15,21 @@ public interface IDistributionApi {
1415

1516
/**
1617
* Check for available updates synchronously (blocking call). This method will block the calling
17-
* thread while making the network request. Consider using checkForUpdate with callback for
18-
* non-blocking behavior.
18+
* thread while making the network request. Consider using checkForUpdate for non-blocking
19+
* behavior.
1920
*
2021
* @return UpdateStatus indicating if an update is available, up to date, or error
2122
*/
2223
@NotNull
2324
UpdateStatus checkForUpdateBlocking();
2425

2526
/**
26-
* Check for available updates asynchronously using a callback.
27+
* Check for available updates asynchronously.
2728
*
28-
* @param onResult Callback that will be called with the UpdateStatus result
29+
* @return Future that will resolve to an UpdateStatus result
2930
*/
30-
void checkForUpdate(@NotNull UpdateCallback onResult);
31+
@NotNull
32+
Future<UpdateStatus> checkForUpdate();
3133

3234
/**
3335
* Download and install the provided update by opening the download URL in the default browser or
@@ -43,9 +45,4 @@ public interface IDistributionApi {
4345
* @return true if the distribution integration is enabled, false otherwise
4446
*/
4547
boolean isEnabled();
46-
47-
/** Callback interface for receiving async update check results. */
48-
interface UpdateCallback {
49-
void onResult(@NotNull UpdateStatus status);
50-
}
5148
}

sentry/src/main/java/io/sentry/NoOpDistributionApi.java

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package io.sentry;
22

3+
import java.util.concurrent.ExecutionException;
4+
import java.util.concurrent.Future;
5+
import java.util.concurrent.TimeUnit;
6+
import java.util.concurrent.TimeoutException;
37
import org.jetbrains.annotations.ApiStatus;
48
import org.jetbrains.annotations.NotNull;
59

@@ -21,8 +25,8 @@ public static NoOpDistributionApi getInstance() {
2125
}
2226

2327
@Override
24-
public void checkForUpdate(@NotNull UpdateCallback onResult) {
25-
// No-op implementation - do nothing
28+
public @NotNull Future<UpdateStatus> checkForUpdate() {
29+
return new CompletedFuture<>(UpdateStatus.UpToDate.getInstance());
2630
}
2731

2832
@Override
@@ -34,4 +38,42 @@ public void downloadUpdate(@NotNull UpdateInfo info) {
3438
public boolean isEnabled() {
3539
return false;
3640
}
41+
42+
/**
43+
* A Future implementation that is already completed with a result. This is used instead of
44+
* CompletableFuture.completedFuture() to maintain compatibility with Android API 21+.
45+
*/
46+
private static final class CompletedFuture<T> implements Future<T> {
47+
private final T result;
48+
49+
CompletedFuture(T result) {
50+
this.result = result;
51+
}
52+
53+
@Override
54+
public boolean cancel(final boolean mayInterruptIfRunning) {
55+
return false;
56+
}
57+
58+
@Override
59+
public boolean isCancelled() {
60+
return false;
61+
}
62+
63+
@Override
64+
public boolean isDone() {
65+
return true;
66+
}
67+
68+
@Override
69+
public T get() throws ExecutionException {
70+
return result;
71+
}
72+
73+
@Override
74+
public T get(final long timeout, final @NotNull TimeUnit unit)
75+
throws ExecutionException, TimeoutException {
76+
return result;
77+
}
78+
}
3779
}

0 commit comments

Comments
 (0)