Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crash NPE while manually tracking views using a Lifecycle observer #439

Open
yamilmedina opened this issue Dec 11, 2024 · 7 comments
Open
Assignees

Comments

@yamilmedina
Copy link

yamilmedina commented Dec 11, 2024

While, we can initialize the Countly SDK and use the tracking system for activities and events.
There is one case where the SDK is simply crashing internally and with that the Application.

It seems to me by looking at the logs there is a missing context in the internals.

Expected Behavior

We should be able to interact with the app, send it to the background, come back, and independently of the state of the SDK, initialized, halted, etc. It should not bubble up a NPE to the consumer app.

Current Behavior

We are receiving a NPE, triggering a crash.

Logs (if appropriate)

 [Countly] onActivitySaveInstanceState, WireActivity
12:36:09.167 Countly                  D  [Countly] onActivityStarted, WireActivity
12:36:09.167 Countly                  D  Countly onStartInternal called, name:[WireActivity], [0] -> [1] activities now open
12:36:09.169 AndroidRuntime           E  FATAL EXCEPTION: main
                                         Process: com.wire.android.internal.debug, PID: 21949
                                         java.lang.NullPointerException: Attempt to read from field 'boolean ly.count.android.sdk.ModuleSessions.manualSessionControlEnabled' on a null object reference in method 'void ly.count.android.sdk.Countly.onStartInternal(android.app.Activity)'
                                         	at ly.count.android.sdk.Countly.onStartInternal(Countly.java:899)
                                         	at ly.count.android.sdk.Countly$7.onActivityStarted(Countly.java:720)
                                         	at android.app.Application.dispatchActivityStarted(Application.java:401)
                                         	at android.app.Activity.dispatchActivityStarted(Activity.java:1452)
                                         	at android.app.Activity.onStart(Activity.java:1991)
                                         	at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:344)
                                         	at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:251)
                                         	at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1582)
                                         	at android.app.Activity.performStart(Activity.java:8628)
                                         	at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3807)
                                         	at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:225)
                                         	at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:205)
                                         	at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:177)
                                         	at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:98)
                                         	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2443)
                                         	at android.os.Handler.dispatchMessage(Handler.java:106)
                                         	at android.os.Looper.loopOnce(Looper.java:205)
                                         	at android.os.Looper.loop(Looper.java:294)
                                         	at android.app.ActivityThread.main(ActivityThread.java:8177)
                                         	at java.lang.reflect.Method.invoke(Native Method)
                                         	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
                                         	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
12:36:09.169 Countly                  D  [ModuleCrash] Uncaught crash handler triggered
12:36:09.177 Countly                  D  [ModuleCrash] Calling crashFilterCheck
12:36:09.177 Countly                  D  [ModuleCrash] sendCrashReportToQueue

Possible Solution

Just simply don't NPE 🙏

More Description

Given we are using Jetpack compose in our project and not the view system, we need to track the screens in the app.

We have a lifecycle observer in the App, that every time it comes to the foreground, it will track the current activity by calling onStart activity. The same when the app is sent to the background, we call onStop.

Your Environment

  • Reproducible 24.4.0 -> 24.7.7
  • Target SDK 15, but reproducible from 9 to latest
@arifBurakDemiray
Copy link
Member

Hi @yamilmedina,

Thank you for bringing this to our attention. I’m sorry to hear that you’re encountering a NullPointerException (NPE) with our SDK.

To help us investigate this issue further, could you please share the configuration code block you’re using for initializing the Countly SDK? Additionally, could you let us know if you’re using the halt or stop methods of the SDK at any point in your implementation?

@arifBurakDemiray arifBurakDemiray self-assigned this Dec 11, 2024
@yamilmedina
Copy link
Author

Thanks @arifBurakDemiray !

This is the config block we use.

override fun configure(
        context: Context,
        analyticsSettings: AnalyticsSettings
    ) {
        if (isConfigured) return

        val countlyConfig = CountlyConfig(
            context,
            analyticsSettings.countlyAppKey,
            analyticsSettings.countlyServerUrl
        ).apply {
            setApplication(context.applicationContext as Application)
            enableTemporaryDeviceIdMode() // Nothing is sent until a proper ID is placed
            setLoggingEnabled(analyticsSettings.enableDebugLogging)
            crashes.apply {
                enableCrashReporting()
            }
            apm.apply {
                enableAppStartTimeTracking()
                enableForegroundBackgroundTracking()
            }
        }

        Countly.sharedInstance().init(countlyConfig)
        Countly.sharedInstance().consent().giveConsent(arrayOf("apm"))

        val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
        val globalSegmentations = mapOf<String, Any>(
            AnalyticsEventConstants.APP_NAME to AnalyticsEventConstants.APP_NAME_ANDROID,
            AnalyticsEventConstants.APP_VERSION to packageInfo.versionName
        )
        Countly.sharedInstance().views().setGlobalViewSegmentation(globalSegmentations)
        isConfigured = true
    }
    ```
    
    
Regarding your second question, yes, we are using the `halt` function whenever we disable the tracking toggle, at the user level option. The stop function, if you refer to `onStop` and `stopViewWithName` then yes, otherwise, no, just the `halt` function

@arifBurakDemiray
Copy link
Member

arifBurakDemiray commented Dec 11, 2024

Hi @yamilmedina,

Thank you for sharing your configuration code—it looks great!

It seems the NullPointerException (NPE) you’re encountering is related to the use of the halt function. Please note that the halt function is intended for testing purposes only and should not be used in production builds. This is because it clears all SDK states, requiring the Countly SDK to be re-initialized afterward. Essentially, using halt behaves as if the app has been completely terminated.

If your goal is to disable tracking, I recommend revoking all consents instead. This is a safer and more suitable approach for production environments. You can achieve this by using the following method:

Countly.sharedInstance().consent().removeConsentAll();  

This will disable all tracking without requiring the SDK to be re-initialized.

If you have any questions or need further clarification, feel free to reach out. We’re here to help!

@yamilmedina
Copy link
Author

yamilmedina commented Dec 11, 2024

Thanks again, one question thought about.

This will disable all tracking without requiring the SDK to be re-initialized.

Does this mean if we use Countly.sharedInstance().consent().removeConsentAll(); that we need to giveConsent() afterward if we want to renable whatever was given, ie: apm ?

EDIT: Sorry we reinit already, so it should be fine, thanks again

@yamilmedina
Copy link
Author

yamilmedina commented Dec 12, 2024

@arifBurakDemiray coming back again with feedback. It is still crashing with the same stacktrace. So using Countly.sharedInstance().consent().removeConsentAll() I'm afraid it does not fixes it. To add more context I add now more logs.

This happens while calling on stop.

12:03:36.856 Countly                  D  [Countly] onActivityStopped, WireActivity
12:03:36.856 Countly                  D  Countly onStopInternal called, [0] -> [-1] activities now open
12:03:36.856 Countly                  E  must call onStart before onStop
12:03:36.857 Countly                  D  [Countly] onActivitySaveInstanceState, WireActivity

@arifBurakDemiray
Copy link
Member

arifBurakDemiray commented Dec 12, 2024

Hello @yamilmedina, sorry for late response. Yes if removeConsentAll used, giveConsent must be used.

And above log indicates that WireActivity's onStart is not called. Could you please validate that?

@arifBurakDemiray
Copy link
Member

arifBurakDemiray commented Dec 12, 2024

Also, I noticed something I missed earlier—if you’d like to use consents, you’ll need to enable consent management first. You can find more details about this in the Countly Android SDK documentation here:
https://support.countly.com/hc/en-us/articles/360037754031-Android#h_01HAVQDM5V1ZB8ECYTH1SPR3Q7

Additionally, I’d like to mention that the Countly SDK automatically registers for activity lifecycle callbacks if the Application class is provided. It listens to the onStart and onStop callbacks to track views. In my experience, the SDK works seamlessly with Jetpack Compose projects, as it is able to catch those views effectively.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants