Skip to content

Memory Leak - MainPaysheetViewModel #1140

@jclemus91

Description

@jclemus91

Braintree SDK Version

4.45.0

Environment

Sandbox

Android Version & Device

Android 14 - Pixel 5

Braintree dependencies

braintree_version = '4.45.0'
"com.braintreepayments.api:paypal-native-checkout:$braintree_version"
"com.braintreepayments.api:data-collector:$braintree_version"

leak_canary_version = '2.14'
"com.squareup.leakcanary:leakcanary-android:$leak_canary_version"

Describe the bug

I've found a memory leak in the sandbox environment (I haven't been able to test production build due to company policies), when using PayPal Native Checkout for Android, Leak Canary identifies the leak related to the com.paypal.pyplcheckout.ui.feature.home.viewmodel.MainPaysheetViewModel, after repeatedly opening and closing the PayPal sheet, it appears that the authListener and doAfterAuth are not being properly released, leading to memory retention.

┬───
│ GC Root: Thread object
│
├─ WV.Yc instance
│    Leaking: NO (PathClassLoader↓ is not leaking)
│    Thread name: 'CleanupReference'
│    ↓ Thread.contextClassLoader
├─ dalvik.system.PathClassLoader instance
│    Leaking: NO (LocalBroadcastManager↓ is not leaking and A ClassLoader is
│    never leaking)
│    ↓ ClassLoader.runtimeInternalObjects
├─ java.lang.Object[] array
│    Leaking: NO (LocalBroadcastManager↓ is not leaking)
│    ↓ Object[3987]
├─ androidx.localbroadcastmanager.content.LocalBroadcastManager class
│    Leaking: NO (a class is never leaking)
│    ↓ static LocalBroadcastManager.mInstance
│                                   ~~~~~~~~~
├─ androidx.localbroadcastmanager.content.LocalBroadcastManager instance
│    Leaking: UNKNOWN
│    Retaining 1.8 kB in 63 objects
│    mAppContext instance of com.**package**..
│    ↓ LocalBroadcastManager.mReceivers
│                            ~~~~~~~~~~
├─ java.util.HashMap instance
│    Leaking: UNKNOWN
│    Retaining 296 B in 14 objects
│    ↓ HashMap[key()]
│             ~~~~~~~
├─ com.paypal.authcore.authentication.Authenticator$a instance
│    Leaking: UNKNOWN
│    Retaining 20 B in 1 objects
│    ↓ Authenticator$a.a
│                      ~
├─ com.paypal.authcore.authentication.Authenticator instance
│    Leaking: UNKNOWN
│    Retaining 13.5 kB in 357 objects
│    j instance of **package**
│    ↓ Authenticator.g
│                    ~
├─ com.paypal.pyplcheckout.flavorauth.
│  WebBasedAuthAccessTokenUseCase$invoke$authDelegate$1 instance
│    Leaking: UNKNOWN
│    Retaining 7.9 kB in 291 objects
│    Anonymous class implementing com.paypal.authcore.authentication.
│    AuthenticationDelegate
│    ↓ WebBasedAuthAccessTokenUseCase$invoke$authDelegate$1.$authListener
│                                                           ~~~~~~~~~~~~~
├─ com.paypal.pyplcheckout.data.repositories.auth.AuthHandler instance
│    Leaking: UNKNOWN
│    Retaining 7.9 kB in 290 objects
│    ↓ AuthHandler.doAfterAuth
│                  ~~~~~~~~~~~
├─ com.paypal.pyplcheckout.ui.feature.home.viewmodel.
│  MainPaysheetViewModel$$ExternalSyntheticLambda19 instance
│    Leaking: UNKNOWN
│    Retaining 7.7 kB in 280 objects
│    ↓ MainPaysheetViewModel$$ExternalSyntheticLambda19.f$0
│                                                       ~~~
╰→ com.paypal.pyplcheckout.ui.feature.home.viewmodel.MainPaysheetViewModel
​  instance
​     Leaking: YES (ObjectWatcher was watching this because com.paypal.
​     pyplcheckout.ui.feature.home.viewmodel.MainPaysheetViewModel received
​     ViewModel#onCleared() callback)
​     Retaining 7.7 kB in 279 objects
​     key = f663730b-63f4-4e57-9cfe-30493c07613e
​     watchDurationMillis = 14555
​     retainedDurationMillis = 9542
METADATA
Build.VERSION.SDK_INT: 34
Build.MANUFACTURER: Google
LeakCanary version: 2.14

The issue is also replicable in the sample app Demo.
demo_sample_app.m4v.zip

┬───
│ GC Root: Thread object
│
├─ android.net.ConnectivityThread instance
│    Leaking: NO (PathClassLoader↓ is not leaking)
│    Thread name: 'ConnectivityThread'
│    ↓ Thread.contextClassLoader
├─ dalvik.system.PathClassLoader instance
│    Leaking: NO (LocalBroadcastManager↓ is not leaking and A ClassLoader is
│    never leaking)
│    ↓ ClassLoader.runtimeInternalObjects
├─ java.lang.Object[] array
│    Leaking: NO (LocalBroadcastManager↓ is not leaking)
│    ↓ Object[5152]
├─ androidx.localbroadcastmanager.content.LocalBroadcastManager class
│    Leaking: NO (a class is never leaking)
│    ↓ static LocalBroadcastManager.mInstance
│                                   ~~~~~~~~~
├─ androidx.localbroadcastmanager.content.LocalBroadcastManager instance
│    Leaking: UNKNOWN
│    Retaining 1.1 kB in 42 objects
│    mAppContext instance of com.braintreepayments.demo.DemoApplication
│    ↓ LocalBroadcastManager.mReceivers
│                            ~~~~~~~~~~
├─ java.util.HashMap instance
│    Leaking: UNKNOWN
│    Retaining 248 B in 11 objects
│    ↓ HashMap[key()]
│             ~~~~~~~
├─ com.paypal.authcore.authentication.Authenticator$a instance
│    Leaking: UNKNOWN
│    Retaining 20 B in 1 objects
│    ↓ Authenticator$a.a
│                      ~
├─ com.paypal.authcore.authentication.Authenticator instance
│    Leaking: UNKNOWN
│    Retaining 13.1 kB in 350 objects
│    j instance of com.braintreepayments.demo.DemoApplication
│    ↓ Authenticator.g
│                    ~
├─ com.paypal.pyplcheckout.flavorauth.
│  WebBasedAuthAccessTokenUseCase$invoke$authDelegate$1 instance
│    Leaking: UNKNOWN
│    Retaining 7.8 kB in 287 objects
│    Anonymous class implementing com.paypal.authcore.authentication.
│    AuthenticationDelegate
│    ↓ WebBasedAuthAccessTokenUseCase$invoke$authDelegate$1.$authListener
│                                                           ~~~~~~~~~~~~~
├─ com.paypal.pyplcheckout.data.repositories.auth.AuthHandler instance
│    Leaking: UNKNOWN
│    Retaining 7.8 kB in 286 objects
│    ↓ AuthHandler.doAfterAuth
│                  ~~~~~~~~~~~
├─ com.paypal.pyplcheckout.ui.feature.home.viewmodel.
│  MainPaysheetViewModel$$ExternalSyntheticLambda2 instance
│    Leaking: UNKNOWN
│    Retaining 7.6 kB in 276 objects
│    ↓ MainPaysheetViewModel$$ExternalSyntheticLambda2.f$0
│                                                      ~~~
╰→ com.paypal.pyplcheckout.ui.feature.home.viewmodel.MainPaysheetViewModel
​  instance
​     Leaking: YES (ObjectWatcher was watching this because com.paypal.
​     pyplcheckout.ui.feature.home.viewmodel.MainPaysheetViewModel received
​     ViewModel#onCleared() callback)
​     Retaining 7.5 kB in 275 objects
​     key = b06b4aab-5c7c-4281-ab13-9cb1be930733
​     watchDurationMillis = 26553
​     retainedDurationMillis = 21553

METADATA

Build.VERSION.SDK_INT: 34
Build.MANUFACTURER: Google
LeakCanary version: 2.14
App process name: com.braintreepayments.demo
Class count: 32964
Instance count: 773845
Primitive array count: 398000
Object array count: 35951
Thread count: 170
Heap total bytes: 75853523
Bitmap count: 1
Bitmap total bytes: 834901
Large bitmap count: 0
Large bitmap total bytes: 0
Db 1: open /data/user/0/com.braintreepayments.demo/databases/analytics_database
Db 2: open /data/user/0/com.braintreepayments.demo/no_backup/androidx.work.
workdb
Stats: LruCache[maxSize=3000,hits=168996,misses=358719,hitRate=32%]
RandomAccess[bytes=26805425,reads=358719,travel=178941312503,range=92814436,size
=105548749]
Analysis duration: 44462 ms

To reproduce

  1. Launch the app with Leak Canary enabled.
  2. Use the Braintree SDK to present the PayPal Native Checkout bottom sheet.
  3. Close the bottom sheet using the "X" button without selecting any options (credit card or ship to).
  4. Repeat steps 2 and 3 multiple times.
  5. Wait for Leak Canary to analyze and observe the reported memory leak.

Expected behavior

After closing the PayPal Native Checkout sheet, all resources should be released properly.

Screenshots

Screenshot 2024-09-04 at 6 15 34 p m

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions