Skip to content

v6 - DropInService memory leak#2638

Open
OscarSpruit wants to merge 2 commits intomainfrom
chore/drop-in-service-leak
Open

v6 - DropInService memory leak#2638
OscarSpruit wants to merge 2 commits intomainfrom
chore/drop-in-service-leak

Conversation

@OscarSpruit
Copy link
Contributor

Description

This PR fixes a memory caused by DropInService by simplifying it's lifecycle management. Using a combination of a started and bound service is overly complex for what we are trying to achieve and there is no need since we are not doing cross process communication. Now DropInService is a started service that we can access through a static registry and we are in full control of it's lifecycle.

Using a combination of a started and bound service is overly complex for what we are trying to achieve and there is no need since we are not doing cross process communication. Now DropInService is a started service that we can access through a static registry and we are in full control of it's lifecycle.
@OscarSpruit OscarSpruit requested a review from a team as a code owner March 17, 2026 11:01
@OscarSpruit OscarSpruit added the Chore [PRs only] Indicates any task that does not need to be mentioned in the public release notes label Mar 17, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the DropInService to resolve a memory leak by streamlining its operational model. The change moves away from a complex started and bound service pattern to a simpler started-only service, which is now managed via a static registry. This ensures that the service's lifecycle is fully controlled, preventing resource accumulation and improving overall stability without impacting its core functionality.

Highlights

  • Simplified DropInService Lifecycle: The DropInService has been refactored from a combination of a started and bound service to solely a started service, simplifying its lifecycle management and removing the need for IBinder for inter-process communication.
  • Memory Leak Fix: This simplification directly addresses a memory leak issue previously present in the DropInService by ensuring better control over its instantiation and destruction.
  • Static Service Registry: A new DropInServiceRegistry static object has been introduced to manage the DropInService instance, allowing access to the service without binding and unbinding.
  • Removal of DropInBinder: The DropInBinder class, previously used for communication with the bound service, has been removed as it is no longer necessary.
  • Updated Service Manager and UI Components: The DropInServiceManager, DropInActivity, and DropInViewModel have been updated to reflect the new service lifecycle, removing all binding-related logic and utilizing the DropInServiceRegistry.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • drop-in/api/drop-in.api
    • Removed onBind method from DropInService's public API.
    • Removed protected abstract modifiers for onAdditionalDetails and onSubmit methods from DropInService's public API, making them implicitly public.
    • Added onCreate and onDestroy methods to DropInService's public API.
  • drop-in/src/main/java/com/adyen/checkout/dropin/DropInService.kt
    • Removed onBind method and its associated DropInBinder instance.
    • Implemented onCreate to register the service with DropInServiceRegistry.
    • Implemented onDestroy to unregister the service from DropInServiceRegistry.
    • Removed protected modifier from onSubmit and onAdditionalDetails abstract functions.
  • drop-in/src/main/java/com/adyen/checkout/dropin/internal/service/DropInBinder.kt
    • Removed the DropInBinder class entirely.
  • drop-in/src/main/java/com/adyen/checkout/dropin/internal/service/DropInServiceManager.kt
    • Removed imports related to ComponentName, ServiceConnection, IBinder, and AdyenLogLevel.
    • Removed the binder field and the ServiceConnection implementation.
    • Renamed startAndBind to start and removed service binding logic.
    • Removed the unbind method.
    • Updated requestOnSubmit and requestOnAdditionalDetails to retrieve the DropInService instance from DropInServiceRegistry instead of a local binder.
  • drop-in/src/main/java/com/adyen/checkout/dropin/internal/service/DropInServiceRegistry.kt
    • Added a new DropInServiceRegistry object to hold and provide access to the DropInService instance.
  • drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInActivity.kt
    • Removed the call to viewModel.unbindDropInService(this) in onDestroy.
  • drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInViewModel.kt
    • Removed the unbindDropInService method.
    • Updated startDropInService to call dropInServiceManager.start instead of startAndBind.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors DropInService to simplify its lifecycle management by moving from a bound service to a started service that uses a static registry. This change effectively addresses the described memory leak. My review focuses on ensuring the new implementation is robust and thread-safe. I've identified a potential race condition in DropInServiceRegistry and a possibility of app crashes in DropInServiceManager under certain conditions. Addressing these points will make the new implementation more resilient.

// Implement a queue similar in functionality to one in v5 DropInActivity.
suspend fun requestOnSubmit(state: PaymentComponentState<*>): CheckoutResult {
return binder?.requestOnSubmit(state) ?: error("Service is not bound")
return DropInServiceRegistry.get()?.onSubmit(state) ?: error("Service is not available")
Copy link
Contributor

Choose a reason for hiding this comment

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

high

Calling error() will crash the application if the service is not available. This could happen if this method is called before the service is fully initialized. A more robust approach is to return a CheckoutResult.Error. This prevents a crash and allows the UI to handle the error gracefully.

Suggested change
return DropInServiceRegistry.get()?.onSubmit(state) ?: error("Service is not available")
return DropInServiceRegistry.get()?.onSubmit(state) ?: CheckoutResult.Error("Service is not available")


suspend fun requestOnAdditionalDetails(data: ActionComponentData): CheckoutResult {
return binder?.requestOnAdditionalDetails(data) ?: error("Service is not bound")
return DropInServiceRegistry.get()?.onAdditionalDetails(data) ?: error("Service is not available")
Copy link
Contributor

Choose a reason for hiding this comment

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

high

Calling error() will crash the application if the service is not available. This could happen if this method is called before the service is fully initialized. A more robust approach is to return a CheckoutResult.Error. This prevents a crash and allows the UI to handle the error gracefully.

Suggested change
return DropInServiceRegistry.get()?.onAdditionalDetails(data) ?: error("Service is not available")
return DropInServiceRegistry.get()?.onAdditionalDetails(data) ?: CheckoutResult.Error("Service is not available")

import com.adyen.checkout.dropin.DropInService

internal object DropInServiceRegistry {
private var service: DropInService? = null
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The service property can be accessed from multiple threads. register() and unregister() are called on the main thread, but get() can be called from background threads. To ensure visibility of changes to service across threads and prevent potential race conditions, it should be marked as @Volatile.

Suggested change
private var service: DropInService? = null
@Volatile private var service: DropInService? = null

@github-actions
Copy link
Contributor

🚫 Public API changes

drop-in

@@ -69,9 +69,11 @@ public abstract interface class com/adyen/checkout/dropin/DropInResultCallback {
public abstract class com/adyen/checkout/dropin/DropInService : androidx/lifecycle/LifecycleService {
public static final field $stable I
public fun <init> ()V
+	public abstract fun onAdditionalDetails (Lcom/adyen/checkout/core/action/data/ActionComponentData;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun onCreate ()V
public fun onDestroy ()V
public fun onStartCommand (Landroid/content/Intent;II)I
+	public abstract fun onSubmit (Lcom/adyen/checkout/core/components/paymentmethod/PaymentComponentState;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class com/adyen/checkout/dropin/internal/ui/ComposableSingletons$ConfirmationDialogKt {

If these changes are intentional run ./gradlew apiDump and commit the changes.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Chore [PRs only] Indicates any task that does not need to be mentioned in the public release notes size:small

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant