From 3bcbaea74aeaba9013fe1bbf2ceeffc89260a2b9 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Wed, 15 Oct 2025 16:47:55 +0200 Subject: [PATCH 1/8] feat(android-distribution): Run checkForUpdate on background thread (EME-413) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../android/distribution/DistributionIntegration.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sentry-android-distribution/src/main/java/io/sentry/android/distribution/DistributionIntegration.kt b/sentry-android-distribution/src/main/java/io/sentry/android/distribution/DistributionIntegration.kt index c1fdf27e2d..0820b5bbdd 100644 --- a/sentry-android-distribution/src/main/java/io/sentry/android/distribution/DistributionIntegration.kt +++ b/sentry-android-distribution/src/main/java/io/sentry/android/distribution/DistributionIntegration.kt @@ -86,12 +86,17 @@ public class DistributionIntegration(context: Context) : Integration, IDistribut /** * Check for available updates asynchronously using a callback. * + * Note: The callback will be invoked on a background thread. If you need to update UI or perform + * main-thread operations, dispatch the result to the main thread yourself. + * * @param onResult Callback that will be called with the UpdateStatus result */ public override fun checkForUpdate(onResult: IDistributionApi.UpdateCallback) { - // TODO implement this in a async way - val result = checkForUpdateBlocking() - onResult.onResult(result) + Thread { + val result = checkForUpdateBlocking() + onResult.onResult(result) + } + .start() } /** From f0ba78d6a53110518ba2da02a73feb20b00a643a Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 16 Oct 2025 08:11:36 +0200 Subject: [PATCH 2/8] refactor(android-distribution): Use Future instead of callback for async checkForUpdate (EME-413) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../api/sentry-android-distribution.api | 2 +- .../distribution/DistributionIntegration.kt | 15 ++++----------- sentry/api/sentry.api | 8 ++------ .../main/java/io/sentry/IDistributionApi.java | 17 +++++++---------- .../java/io/sentry/NoOpDistributionApi.java | 6 ++++-- 5 files changed, 18 insertions(+), 30 deletions(-) diff --git a/sentry-android-distribution/api/sentry-android-distribution.api b/sentry-android-distribution/api/sentry-android-distribution.api index f32afca784..b14aaec9eb 100644 --- a/sentry-android-distribution/api/sentry-android-distribution.api +++ b/sentry-android-distribution/api/sentry-android-distribution.api @@ -1,6 +1,6 @@ public final class io/sentry/android/distribution/DistributionIntegration : io/sentry/IDistributionApi, io/sentry/Integration { public fun (Landroid/content/Context;)V - public fun checkForUpdate (Lio/sentry/IDistributionApi$UpdateCallback;)V + public fun checkForUpdate ()Ljava/util/concurrent/Future; public fun checkForUpdateBlocking ()Lio/sentry/UpdateStatus; public fun downloadUpdate (Lio/sentry/UpdateInfo;)V public fun isEnabled ()Z diff --git a/sentry-android-distribution/src/main/java/io/sentry/android/distribution/DistributionIntegration.kt b/sentry-android-distribution/src/main/java/io/sentry/android/distribution/DistributionIntegration.kt index 0820b5bbdd..29fd1d7247 100644 --- a/sentry-android-distribution/src/main/java/io/sentry/android/distribution/DistributionIntegration.kt +++ b/sentry-android-distribution/src/main/java/io/sentry/android/distribution/DistributionIntegration.kt @@ -84,19 +84,12 @@ public class DistributionIntegration(context: Context) : Integration, IDistribut } /** - * Check for available updates asynchronously using a callback. + * Check for available updates asynchronously. * - * Note: The callback will be invoked on a background thread. If you need to update UI or perform - * main-thread operations, dispatch the result to the main thread yourself. - * - * @param onResult Callback that will be called with the UpdateStatus result + * @return Future that will resolve to an UpdateStatus result */ - public override fun checkForUpdate(onResult: IDistributionApi.UpdateCallback) { - Thread { - val result = checkForUpdateBlocking() - onResult.onResult(result) - } - .start() + public override fun checkForUpdate(): java.util.concurrent.Future { + return sentryOptions.executorService.submit { checkForUpdateBlocking() } } /** diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index f49e035f5f..bbecb980c5 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -776,16 +776,12 @@ public abstract interface class io/sentry/IContinuousProfiler { } public abstract interface class io/sentry/IDistributionApi { - public abstract fun checkForUpdate (Lio/sentry/IDistributionApi$UpdateCallback;)V + public abstract fun checkForUpdate ()Ljava/util/concurrent/Future; public abstract fun checkForUpdateBlocking ()Lio/sentry/UpdateStatus; public abstract fun downloadUpdate (Lio/sentry/UpdateInfo;)V public abstract fun isEnabled ()Z } -public abstract interface class io/sentry/IDistributionApi$UpdateCallback { - public abstract fun onResult (Lio/sentry/UpdateStatus;)V -} - public abstract interface class io/sentry/IEnvelopeReader { public abstract fun read (Ljava/io/InputStream;)Lio/sentry/SentryEnvelope; } @@ -1494,7 +1490,7 @@ public final class io/sentry/NoOpContinuousProfiler : io/sentry/IContinuousProfi } public final class io/sentry/NoOpDistributionApi : io/sentry/IDistributionApi { - public fun checkForUpdate (Lio/sentry/IDistributionApi$UpdateCallback;)V + public fun checkForUpdate ()Ljava/util/concurrent/Future; public fun checkForUpdateBlocking ()Lio/sentry/UpdateStatus; public fun downloadUpdate (Lio/sentry/UpdateInfo;)V public static fun getInstance ()Lio/sentry/NoOpDistributionApi; diff --git a/sentry/src/main/java/io/sentry/IDistributionApi.java b/sentry/src/main/java/io/sentry/IDistributionApi.java index 953bc6b893..fc308009f2 100644 --- a/sentry/src/main/java/io/sentry/IDistributionApi.java +++ b/sentry/src/main/java/io/sentry/IDistributionApi.java @@ -1,5 +1,6 @@ package io.sentry; +import java.util.concurrent.Future; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -14,8 +15,8 @@ public interface IDistributionApi { /** * Check for available updates synchronously (blocking call). This method will block the calling - * thread while making the network request. Consider using checkForUpdate with callback for - * non-blocking behavior. + * thread while making the network request. Consider using checkForUpdate for non-blocking + * behavior. * * @return UpdateStatus indicating if an update is available, up to date, or error */ @@ -23,11 +24,12 @@ public interface IDistributionApi { UpdateStatus checkForUpdateBlocking(); /** - * Check for available updates asynchronously using a callback. + * Check for available updates asynchronously. * - * @param onResult Callback that will be called with the UpdateStatus result + * @return Future that will resolve to an UpdateStatus result */ - void checkForUpdate(@NotNull UpdateCallback onResult); + @NotNull + Future checkForUpdate(); /** * Download and install the provided update by opening the download URL in the default browser or @@ -43,9 +45,4 @@ public interface IDistributionApi { * @return true if the distribution integration is enabled, false otherwise */ boolean isEnabled(); - - /** Callback interface for receiving async update check results. */ - interface UpdateCallback { - void onResult(@NotNull UpdateStatus status); - } } diff --git a/sentry/src/main/java/io/sentry/NoOpDistributionApi.java b/sentry/src/main/java/io/sentry/NoOpDistributionApi.java index 3215a192e0..89b4e8de03 100644 --- a/sentry/src/main/java/io/sentry/NoOpDistributionApi.java +++ b/sentry/src/main/java/io/sentry/NoOpDistributionApi.java @@ -1,5 +1,7 @@ package io.sentry; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -21,8 +23,8 @@ public static NoOpDistributionApi getInstance() { } @Override - public void checkForUpdate(@NotNull UpdateCallback onResult) { - // No-op implementation - do nothing + public @NotNull Future checkForUpdate() { + return CompletableFuture.completedFuture(UpdateStatus.UpToDate.getInstance()); } @Override From d3b97ebf9067cc861ba3ad13c0dd0e24e50487b2 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 16 Oct 2025 08:46:33 +0200 Subject: [PATCH 3/8] refactor(sentry): Use custom CompletedFuture for API 21+ compatibility (EME-413) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../java/io/sentry/NoOpDistributionApi.java | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/sentry/src/main/java/io/sentry/NoOpDistributionApi.java b/sentry/src/main/java/io/sentry/NoOpDistributionApi.java index 89b4e8de03..f4380a02fd 100644 --- a/sentry/src/main/java/io/sentry/NoOpDistributionApi.java +++ b/sentry/src/main/java/io/sentry/NoOpDistributionApi.java @@ -1,7 +1,9 @@ package io.sentry; -import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -24,7 +26,7 @@ public static NoOpDistributionApi getInstance() { @Override public @NotNull Future checkForUpdate() { - return CompletableFuture.completedFuture(UpdateStatus.UpToDate.getInstance()); + return new CompletedFuture<>(UpdateStatus.UpToDate.getInstance()); } @Override @@ -36,4 +38,42 @@ public void downloadUpdate(@NotNull UpdateInfo info) { public boolean isEnabled() { return false; } + + /** + * A Future implementation that is already completed with a result. This is used instead of + * CompletableFuture.completedFuture() to maintain compatibility with Android API 21+. + */ + private static final class CompletedFuture implements Future { + private final T result; + + CompletedFuture(T result) { + this.result = result; + } + + @Override + public boolean cancel(final boolean mayInterruptIfRunning) { + return false; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return true; + } + + @Override + public T get() throws ExecutionException { + return result; + } + + @Override + public T get(final long timeout, final @NotNull TimeUnit unit) + throws ExecutionException, TimeoutException { + return result; + } + } } From 4b7d5146ffed55dfd62103aad3788d63d5dd86fa Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 16 Oct 2025 14:38:42 +0200 Subject: [PATCH 4/8] refactor(android-distribution): Import Future type for cleaner code (EME-413) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../io/sentry/android/distribution/DistributionIntegration.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sentry-android-distribution/src/main/java/io/sentry/android/distribution/DistributionIntegration.kt b/sentry-android-distribution/src/main/java/io/sentry/android/distribution/DistributionIntegration.kt index 29fd1d7247..daf702263f 100644 --- a/sentry-android-distribution/src/main/java/io/sentry/android/distribution/DistributionIntegration.kt +++ b/sentry-android-distribution/src/main/java/io/sentry/android/distribution/DistributionIntegration.kt @@ -14,6 +14,7 @@ import io.sentry.UpdateInfo import io.sentry.UpdateStatus import java.net.SocketTimeoutException import java.net.UnknownHostException +import java.util.concurrent.Future import org.jetbrains.annotations.ApiStatus /** @@ -88,7 +89,7 @@ public class DistributionIntegration(context: Context) : Integration, IDistribut * * @return Future that will resolve to an UpdateStatus result */ - public override fun checkForUpdate(): java.util.concurrent.Future { + public override fun checkForUpdate(): Future { return sentryOptions.executorService.submit { checkForUpdateBlocking() } } From 64832c858d17c79d019f3f9ab9e4193ea352f73d Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 16 Oct 2025 15:22:24 +0200 Subject: [PATCH 5/8] fix(samples): Update MainActivity to use Future-based checkForUpdate API (EME-413) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../sentry/samples/android/MainActivity.java | 71 +++++++++++-------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java index 0987dafe3f..89ecb5b5e5 100644 --- a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java +++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java @@ -315,35 +315,48 @@ public void run() { binding.checkForUpdate.setOnClickListener( view -> { Toast.makeText(this, "Checking for updates...", Toast.LENGTH_SHORT).show(); - Sentry.distribution() - .checkForUpdate( - result -> { - runOnUiThread( - () -> { - String message; - if (result instanceof UpdateStatus.NewRelease) { - UpdateStatus.NewRelease newRelease = (UpdateStatus.NewRelease) result; - message = - "Update available: " - + newRelease.getInfo().getBuildVersion() - + " (Build " - + newRelease.getInfo().getBuildNumber() - + ")\nDownload URL: " - + newRelease.getInfo().getDownloadUrl(); - } else if (result instanceof UpdateStatus.UpToDate) { - message = "App is up to date!"; - } else if (result instanceof UpdateStatus.NoNetwork) { - UpdateStatus.NoNetwork noNetwork = (UpdateStatus.NoNetwork) result; - message = "No network connection: " + noNetwork.getMessage(); - } else if (result instanceof UpdateStatus.UpdateError) { - UpdateStatus.UpdateError error = (UpdateStatus.UpdateError) result; - message = "Error checking for updates: " + error.getMessage(); - } else { - message = "Unknown status"; - } - Toast.makeText(this, message, Toast.LENGTH_LONG).show(); - }); - }); + java.util.concurrent.Future future = Sentry.distribution().checkForUpdate(); + // Process result on background thread, then update UI + new Thread( + () -> { + try { + UpdateStatus result = future.get(); + runOnUiThread( + () -> { + String message; + if (result instanceof UpdateStatus.NewRelease) { + UpdateStatus.NewRelease newRelease = (UpdateStatus.NewRelease) result; + message = + "Update available: " + + newRelease.getInfo().getBuildVersion() + + " (Build " + + newRelease.getInfo().getBuildNumber() + + ")\nDownload URL: " + + newRelease.getInfo().getDownloadUrl(); + } else if (result instanceof UpdateStatus.UpToDate) { + message = "App is up to date!"; + } else if (result instanceof UpdateStatus.NoNetwork) { + UpdateStatus.NoNetwork noNetwork = (UpdateStatus.NoNetwork) result; + message = "No network connection: " + noNetwork.getMessage(); + } else if (result instanceof UpdateStatus.UpdateError) { + UpdateStatus.UpdateError error = (UpdateStatus.UpdateError) result; + message = "Error checking for updates: " + error.getMessage(); + } else { + message = "Unknown status"; + } + Toast.makeText(this, message, Toast.LENGTH_LONG).show(); + }); + } catch (Exception e) { + runOnUiThread( + () -> + Toast.makeText( + this, + "Error checking for updates: " + e.getMessage(), + Toast.LENGTH_LONG) + .show()); + } + }) + .start(); }); binding.openCameraActivity.setOnClickListener( From 9c228e46c5df6b63b790a87bd50ef513eeab0943 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 17 Oct 2025 16:48:36 +0200 Subject: [PATCH 6/8] refactor(samples): Add Future import and async library comment (EME-413) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../main/java/io/sentry/samples/android/MainActivity.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java index 89ecb5b5e5..5ebc8c2716 100644 --- a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java +++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java @@ -29,6 +29,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; import timber.log.Timber; public class MainActivity extends AppCompatActivity { @@ -315,7 +316,9 @@ public void run() { binding.checkForUpdate.setOnClickListener( view -> { Toast.makeText(this, "Checking for updates...", Toast.LENGTH_SHORT).show(); - java.util.concurrent.Future future = Sentry.distribution().checkForUpdate(); + Future future = Sentry.distribution().checkForUpdate(); + // In production, convert this to use your preferred async library (RxJava, Coroutines, etc.) + // This sample uses raw threads and Future.get() for simplicity // Process result on background thread, then update UI new Thread( () -> { From 8f3b337f4758c4762aad7a1eac2157a24887852d Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 17 Oct 2025 17:16:01 +0200 Subject: [PATCH 7/8] style(samples): Reflow comment for proper line length (EME-413) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Applied spotlessApply formatting to wrap long comment line. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../src/main/java/io/sentry/samples/android/MainActivity.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java index 5ebc8c2716..68372c7ab1 100644 --- a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java +++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java @@ -317,7 +317,8 @@ public void run() { view -> { Toast.makeText(this, "Checking for updates...", Toast.LENGTH_SHORT).show(); Future future = Sentry.distribution().checkForUpdate(); - // In production, convert this to use your preferred async library (RxJava, Coroutines, etc.) + // In production, convert this to use your preferred async library (RxJava, Coroutines, + // etc.) // This sample uses raw threads and Future.get() for simplicity // Process result on background thread, then update UI new Thread( From 9b430e8dba1a224a9d68ca2ac7537b44de3ab760 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Fri, 17 Oct 2025 15:23:39 +0000 Subject: [PATCH 8/8] release: 8.24.0-alpha.2 --- CHANGELOG.md | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bd989eafd..4bacd32c46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 8.24.0-alpha.2 ### Features diff --git a/gradle.properties b/gradle.properties index 5637a2f35c..8812653a85 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled android.useAndroidX=true # Release information -versionName=8.23.0 +versionName=8.24.0-alpha.2 # Override the SDK name on native crashes on Android sentryAndroidSdkName=sentry.native.android