appsflyer-react-native-plugin icon indicating copy to clipboard operation
appsflyer-react-native-plugin copied to clipboard

Android Crash - "Callback arg cannot be called more than once";

Open mobinni opened this issue 1 year ago • 14 comments

Report

Plugin Version

6.15.1

On what Platform are you having the issue?

Android only

What did you do?

We use logEvent

What did you expect to happen?

logEvent callbacks not to crash on Android

What happened instead?

On new arch in production for Android we receive crash logs where onError callback is called, but invoked twice which causes a crash to trigger;

react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp
      if (!callback) {
          LOG(FATAL) << "Callback arg cannot be called more than once";
          return;
        }
  #10  pc 0x000000000060cb00  /data/app/~~fd4r20TdR3MOOG2sjAaVtg==/com.wealthsimple.trade-dfI26diei0-qwrUPNR0eeg==/base.apk (com.facebook.react.bridge.CxxCallbackImpl.invoke+8)
  #11  pc 0x0000000000780ee4  /apex/com.android.art/lib64/libart.so (nterp_helper+7540)
  #12  pc 0x0000000000879a18  /data/app/~~fd4r20TdR3MOOG2sjAaVtg==/com.*.*-dfI26diei0-qwrUPNR0eeg==/base.apk (com.appsflyer.reactnative.RNAppsFlyerModule$3.onError+12)
  #13  pc 0x0000000000780ee4  /apex/com.android.art/lib64/libart.so (nterp_helper+7540)
  #14  pc 0x0000000000861500  /data/app/~~fd4r20TdR3MOOG2sjAaVtg==/com.*.*-dfI26diei0-qwrUPNR0eeg==/base.apk (com.appsflyer.internal.AFf1pSDK.AFAdRevenueData+116)

Please provide any other relevant information.

Im not sure how the double invocation happens, based on google play console it happens mostly on backgrounding. I could repro the issue by manually double invoking a callback. Based on the codepaths I see that we trigger the error callback in the try / catch as well as onError, there might be some type of condition being hit here where the error happens in logEvent natively gets thrown up and handled again?

mobinni avatar Jan 15 '25 21:01 mobinni

@amit-kremer93

mobinni avatar Feb 05 '25 10:02 mobinni

Same with 6.15.3 (expo 52.0.31), we're slowly rolling out a new version and seeing a few of these

The stack trace looks like this

==/base.apk (com.facebook.react.bridge.CxxCallbackImpl.invoke+8)
==/base.apk (com.appsflyer.reactnative.RNAppsFlyerModule$3.onError+12)
==/base.apk (com.appsflyer.internal.AFf1oSDK.getCurrencyIso4217Code+116)
==/base.apk (com.appsflyer.internal.AFe1eSDK.component3+96)

markstreich avatar Feb 17 '25 11:02 markstreich

@markstreich we disabled the double callback as a local patch for remediation

mobinni avatar Feb 19 '25 23:02 mobinni

@mobinni could you share the patch?

markstreich avatar Feb 20 '25 04:02 markstreich

Something like this? (I don't know how to recreate it, and don't know java, so kind of flying blind)

--- a/node_modules/react-native-appsflyer/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerModule.java
+++ b/node_modules/react-native-appsflyer/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerModule.java
@@ -326,7 +326,7 @@ public class RNAppsFlyerModule extends ReactContextBaseJavaModule {
                 });
             }
         } catch (Exception e) {
-            errorCallback.invoke(e.getMessage());
+            // errorCallback.invoke(e.getMessage());
             return;
         }
     }

markstreich avatar Feb 20 '25 10:02 markstreich

diff --git a/node_modules/react-native-appsflyer/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerModule.java b/node_modules/react-native-appsflyer/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerModule.java
index 3892ba5..51cca6b 100755
--- a/node_modules/react-native-appsflyer/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerModule.java
+++ b/node_modules/react-native-appsflyer/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerModule.java
@@ -304,7 +304,8 @@ public class RNAppsFlyerModule extends ReactContextBaseJavaModule {
             final Callback errorCallback) {
         try {
             if (eventName.trim().equals("")) {
-                errorCallback.invoke(NO_EVENT_NAME_FOUND);
+                // Disable as this is not a valuable error callback
+                // errorCallback.invoke(NO_EVENT_NAME_FOUND);
                 return;
             }
             Map<String, Object> data = RNUtil.toMap(eventData);
@@ -316,18 +317,17 @@ public class RNAppsFlyerModule extends ReactContextBaseJavaModule {
                 AppsFlyerLib.getInstance().logEvent(getCurrentActivity(), eventName, data, new AppsFlyerRequestListener() {
                     @Override
                     public void onSuccess() {
-                        successCallback.invoke(SUCCESS);
+                        // successCallback.invoke(SUCCESS);
                     }
 
                     @Override
                     public void onError(int i, @NonNull String s) {
-                        errorCallback.invoke(s);
+                        // errorCallback.invoke(s);
                     }
                 });
             }
         } catch (Exception e) {
-            errorCallback.invoke(e.getMessage());
-            return;
+            // Do nothing
         }
     }
 
@@ -336,7 +336,8 @@ public class RNAppsFlyerModule extends ReactContextBaseJavaModule {
             final String eventName, ReadableMap eventData, final Promise promise) {
         try {
             if (eventName.trim().equals("")) {
-                promise.reject(NO_EVENT_NAME_FOUND, new Exception(NO_EVENT_NAME_FOUND).getMessage());
+                // Disable as this is not a valuable error callback
+                // promise.reject(NO_EVENT_NAME_FOUND, new Exception(NO_EVENT_NAME_FOUND).getMessage());
                 return;
             }
             Map<String, Object> data = RNUtil.toMap(eventData);
@@ -348,22 +349,21 @@ public class RNAppsFlyerModule extends ReactContextBaseJavaModule {
                 AppsFlyerLib.getInstance().logEvent(getCurrentActivity(), eventName, data, new AppsFlyerRequestListener() {
                     @Override
                     public void onSuccess() {
-                        promise.resolve(SUCCESS);
+                        // promise.resolve(SUCCESS);
                     }
 
                     @Override
                     public void onError(int i, @NonNull String s) {
-                        promise.reject(s);
+                        // promise.reject(s);
                     }
                 });
             }
         } catch (Exception e) {
-            promise.reject(UNKNOWN_ERROR, e);
-            return;
+            // Do nothing
         }
     }

mobinni avatar Feb 20 '25 17:02 mobinni

Thanks!

markstreich avatar Feb 20 '25 17:02 markstreich

@al-af can you please take a look at this

mobinni avatar Mar 30 '25 01:03 mobinni

Same with 6.15.3 (expo 52.0.31), we're slowly rolling out a new version and seeing a few of these

The stack trace looks like this

==/base.apk (com.facebook.react.bridge.CxxCallbackImpl.invoke+8)
==/base.apk (com.appsflyer.reactnative.RNAppsFlyerModule$3.onError+12)
==/base.apk (com.appsflyer.internal.AFf1oSDK.getCurrencyIso4217Code+116)
==/base.apk (com.appsflyer.internal.AFe1eSDK.component3+96)

I have the same problem too (with version 6.16.2, expo 52.0.46), I don't understand why in the crash logs there is a reference to this method: getCurrencyIso4217Code when it doesn't exist in the React Native plugin or in the Android SDK.

I have hundreds of crashes in production because of this.

See my crashlytics report

com.appsflyer.reactnative.RNAppsFlyerModule$3.onError + 12

com.appsflyer.internal.AFf1oSDK.getCurrencyIso4217Code + 116

com.appsflyer.internal.AFe1eSDK.component3 + 96

com.appsflyer.internal.AFe1aSDK$4.run + 210

jbcrestot avatar Jun 06 '25 13:06 jbcrestot

This might be deep inside appsflyer itself - https://github.com/search?q=org%3AAppsFlyerSDK%20getCurrencyIso4217Code&type=code

mobinni avatar Jun 11 '25 01:06 mobinni

At least for me, one kind of "solution" to this issue, is to use the non-callback version of the logEvent function, since there are these two signatures for the function:

logEvent(eventName: string, eventValues: object): Promise<string>;
    
logEvent(
      eventName: string,
      eventValues: object,
      successC: SuccessCB,
      errorC: ErrorCB
    ): void;

If I only give the first two parameters to the function, then it uses the version that returns a promise and you can get the result from the promise, and that version of the function doesn't seem to have this issue, probably because it doesn't do callbacks.

soutua avatar Aug 05 '25 12:08 soutua

I was able to reproduce the issue by disabling the device’s internet connection, opening the app, and performing the action that triggers AppsFlyer’s logEvent multiple times. From my investigation, it seems the library keeps references to old AppsFlyerRequestListener instances internally, so when an error occurs, it invokes those stale listeners again, which eventually causes the crash.

Here is my patch, hope it helps:

diff --git a/node_modules/react-native-appsflyer/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerModule.java b/node_modules/react-native-appsflyer/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerModule.java
index bb77d93..dbe7a93 100755
--- a/node_modules/react-native-appsflyer/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerModule.java
+++ b/node_modules/react-native-appsflyer/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerModule.java
@@ -54,6 +54,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Iterator;
 import java.util.Locale;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import static com.appsflyer.reactnative.RNAppsFlyerConstants.*;
 import static com.appsflyer.reactnative.RNAppsFlyerConstants.afOnDeepLinking;
@@ -323,14 +324,20 @@ public class RNAppsFlyerModule extends ReactContextBaseJavaModule {
             Activity currentActivity = getCurrentActivity();
             if (currentActivity != null) {
                 AppsFlyerLib.getInstance().logEvent(getCurrentActivity(), eventName, data, new AppsFlyerRequestListener() {
+                    final AtomicBoolean isCompleted = new AtomicBoolean(false);
+
                     @Override
                     public void onSuccess() {
-                        successCallback.invoke(SUCCESS);
+                        if (isCompleted.compareAndSet(false, true)) {
+                            successCallback.invoke(SUCCESS);
+                        }
                     }
 
                     @Override
                     public void onError(int i, @NonNull String s) {
-                        errorCallback.invoke(s);
+                        if (isCompleted.compareAndSet(false, true)) {
+                            errorCallback.invoke(String.valueOf(i), s);
+                        }
                     }
                 });
             }
@@ -355,14 +362,20 @@ public class RNAppsFlyerModule extends ReactContextBaseJavaModule {
             Activity currentActivity = getCurrentActivity();
             if (currentActivity != null) {
                 AppsFlyerLib.getInstance().logEvent(getCurrentActivity(), eventName, data, new AppsFlyerRequestListener() {
+                    final AtomicBoolean isCompleted = new AtomicBoolean(false);
+
                     @Override
                     public void onSuccess() {
-                        promise.resolve(SUCCESS);
+                        if (isCompleted.compareAndSet(false, true)) {
+                            promise.resolve(SUCCESS);
+                        }
                     }
 
                     @Override
                     public void onError(int i, @NonNull String s) {
-                        promise.reject(s);
+                        if (isCompleted.compareAndSet(false, true)) {
+                            promise.reject(String.valueOf(i), s);
+                        }
                     }
                 });
             }

TinhHuynh avatar Sep 19 '25 12:09 TinhHuynh

I have same issue:

2025-09-19 11:45:47.967 10827-10827 DEBUG                   pid-10827                            A        #27 pc 0000000000006680  [anon:dalvik-classes12.dex extracted in memory from /data/app/~~vRxBu-fQnWBeoXmmm8vdCA==/PACKAGE-KkfU5H2oxLWpJPHPmZV8VQ==/base.apk!classes12.dex] (com.appsflyer.reactnative.RNAppsFlyerModule$3.onError+12)
2025-09-19 11:45:47.967 10827-10827 DEBUG                   pid-10827                            A        #30 pc 00000000004074fe  [anon:dalvik-classes21.dex extracted in memory from /data/app/~~vRxBu-fQnWBeoXmmm8vdCA==/PACKAGE-KkfU5H2oxLWpJPHPmZV8VQ==/base.apk!classes21.dex] (com.appsflyer.internal.AFe1eSDK.getMediationNetwork+146)
2025-09-19 11:45:47.967 10827-10827 DEBUG                   pid-10827                            A        #33 pc 0000000000409130  [anon:dalvik-classes21.dex extracted in memory from /data/app/~~vRxBu-fQnWBeoXmmm8vdCA==/PACKAGE-KkfU5H2oxLWpJPHPmZV8VQ==/base.apk!classes21.dex] (com.appsflyer.internal.AFe1lSDK.component3+96)
2025-09-19 11:45:47.967 10827-10827 DEBUG                   pid-10827                            A        #36 pc 0000000000409c92  [anon:dalvik-classes21.dex extracted in memory from /data/app/~~vRxBu-fQnWBeoXmmm8vdCA==/PACKAGE-KkfU5H2oxLWpJPHPmZV8VQ==/base.apk!classes21.dex] (com.appsflyer.internal.AFe1nSDK$4.run+210)

alanneri-digitalfemsa avatar Sep 19 '25 17:09 alanneri-digitalfemsa

https://github.com/AppsFlyerSDK/appsflyer-react-native-plugin/issues/601#issuecomment-3311929157

This works :)

Thanks @TinhHuynh

swayanshu-apbr avatar Dec 04 '25 10:12 swayanshu-apbr