Skip to content

Blanket activity null check in onMethodCall blocks methods that only need Context #434

@dfdgsdfg

Description

@dfdgsdfg

Bug Description

AppsflyerSdkPlugin.onMethodCall() has a blanket activity == null check at the top (line 210) that rejects all method calls when no Activity is attached:

@Override
public void onMethodCall(MethodCall call, Result result) {
    if (activity == null) {
        result.error("NO_ACTIVITY", "The current activity is null", null);
        return;
    }
    // ... all methods blocked
}

However, many methods — including initSdk — only need mContext (application context), not activity. For example, initSdk() calls:

instance.init(afDevKey, gcdListener, mContext);  // uses mContext, not activity

The only call that actually needs Activity inside initSdk is instance.start(activity), and only when isManualStartMode is false.

Impact

This causes PlatformException(NO_ACTIVITY, The current activity is null, null, null) crashes on Android when:

  1. App startup before runApp() — Flutter plugins are initialized during DI setup before the Activity is created. AppsFlyer initSdk is called during this window.
  2. Background execution — When the app process is started by a Service (e.g., FCM background message handling), there is no Activity at all.

In our production app (Android, Flutter), this is the #1 crash with 26k+ events affecting 13.5k+ users, with 93% occurring during early startup (EARLY signal in Crashlytics).

Expected Behavior

Methods that only require Context should work without an Activity. The activity == null guard should be moved to only the methods that actually need it:

  • start() / startSDK() / startSDKwithHandler()
  • performOnDeepLinking() (needs activity.getIntent())
  • sendPushNotificationData() (needs activity.getIntent())
  • setPushNotification() (passes activity to SDK)

All other methods (including initSdk with isManualStartMode: true, logEvent, getAppsFlyerUID, setCustomerUserId, etc.) use mContext and should not be blocked.

Suggested Fix

Move the Activity null check from onMethodCall() into only the methods that require it:

@Override
public void onMethodCall(MethodCall call, Result result) {
    final String method = call.method;
    switch (method) {
        case "initSdk":
            initSdk(call, result);  // uses mContext for init, activity only for start()
            break;
        case "startSDK":
            if (activity == null) {
                result.error("NO_ACTIVITY", "The current activity is null", null);
                return;
            }
            startSDK(call, result);
            break;
        // ... etc
    }
}

And inside initSdk, guard only the start() call:

if (!isManualStartMode) {
    if (activity != null) {
        instance.start(activity);
    } else {
        Log.w(AF_PLUGIN_TAG, "Activity not available, skipping auto-start. Call startSDK() after Activity is ready.");
    }
}

Environment

  • appsflyer_sdk: 6.17.8
  • Flutter: 3.x (stable)
  • Android: Reproducible on all versions, especially Android 16
  • Crash location: StandardMethodCodec.decodeEnvelopePlatformException(NO_ACTIVITY)

Related

The same blanket guard pattern exists in performOnDeepLinking() (line 383-385) but that one legitimately needs Activity. The issue is specifically with the top-level guard blocking methods that don't need it.

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