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

[Bug]: Version 4.8.5 breaks Hilt dependency injection of CoroutineWorkers #1748

Closed
1 task done
sebastinto opened this issue Mar 13, 2023 · 19 comments · Fixed by #2122
Closed
1 task done

[Bug]: Version 4.8.5 breaks Hilt dependency injection of CoroutineWorkers #1748

sebastinto opened this issue Mar 13, 2023 · 19 comments · Fixed by #2122

Comments

@sebastinto
Copy link

sebastinto commented Mar 13, 2023

What happened?

I've recently started experiencing java.lang.NoSuchMethodException when building CoroutineWorker classes.

The issue disappears when specifying version 4.8.4 of the OneSignal Android SDK in libs.version.toml. So instead of declaring the dependency as onesignal = "[4.0.0, 4.99.99]", declaring it as onesignal = "4.8.4".

Only release builds seem to be affected. Debug builds or release builds with the isDebuggable property set to true in the app-level build.gradle.kts work fine with the latest version of the SDK.

Maybe more relevant for #1672, but adding a breakpoint at line 55 of HiltWorkerFactory seems to indicate that when Hilt attempts to instantiate com.onesignal.OSNotificationRestoreWorkManager$NotificationRestoreWorker or com.onesignal.OSFocusHandler$OnLostFocusWorker, the factory is null. When using the app's CoroutineWorkers the breakpoint is not hit.

I'm using work = "2.8.0" and hilt = "2.45"

My app class:

@ExperimentalCoroutinesApi
@HiltAndroidApp
class MyApplication : Application(), Configuration.Provider {

    // Allows injecting CoroutineWorkers
    @Inject
    lateinit var workerFactory: HiltWorkerFactory

    override fun getWorkManagerConfiguration() =
        Configuration.Builder()
            .setWorkerFactory(workerFactory)
            .build()

    @Inject
    lateinit var sharedPreferences: SharedPreferences

    @Inject
    lateinit var oneSignal: MyAppOneSignal

    override fun onCreate() {
        super.onCreate()

        val defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()

        Thread.setDefaultUncaughtExceptionHandler(
            defaultUncaughtExceptionHandler?.let {
                CustomUncaughtExceptionHandler(it)
            }
        )

        oneSignal.init(context = this)

        if (BuildConfig.ENABLE_LOGGING) Timber.plant(ExtendedDebugTree())

        with(sharedPreferences) {
            resetTooltips()
            enableInAppUpdatePrompt()
        }
    }

}

I have this in my AndroidManifest.xml file:

        <provider
            android:name="androidx.startup.InitializationProvider"
            android:authorities="${applicationId}.androidx-startup"
            android:exported="false"
            tools:node="merge">

            <meta-data
                android:name="androidx.work.WorkManagerInitializer"
                android:value="androidx.startup"
                tools:node="remove" />

        </provider>

Example of how I declare and enqueue one of the CoroutineWorkers that fail (although I don't think this is relevant)

val myWorker = OneTimeWorkRequestBuilder<MyWorker>()
            .addTag(MY_WORKER_TAG + someData)
            .setInputData(workDataOf(SOME_DATA to someData))
            .setBackoffCriteria(BackoffPolicy.LINEAR, WORK_REQUEST_DEFAULT_BACKOFF_TIME, TimeUnit.MILLISECONDS)
            .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
            .build()

val uniqueWorkName = MY_WORKER_WORK_TAG + "_" + someData + "_" + someOtherData

workManager
            .beginUniqueWork(uniqueWorkName, ExistingWorkPolicy.APPEND_OR_REPLACE, myWorker)
            .then(myOtherWorker)
            .enqueue()

workManager is injected here:

@Module
@InstallIn(SingletonComponent::class)
class WorkModule {

    @Singleton
    @Provides
    fun provideWorkManager(@ApplicationContext context: Context): WorkManager =
        WorkManager.getInstance(context)
}

Steps to reproduce?

N/A

What did you expect to happen?

I expected OneSignal to not interfere with CoroutineWorkers outside of its library 😬

OneSignal Android SDK version

Release 4.8.5

Android version

13, 10

Specific Android models

* Samsung S9
* SDK 29

* Pixel 7 Pro
* SDK 33

Relevant log output

Could not instantiate com.app.MyWorker
java.lang.NoSuchMethodException: com.app.MyWorker.<init> [class android.content.Context, class androidx.work.WorkerParameters]
    at java.lang.Class.getConstructor0(Class.java:2332)
    at java.lang.Class.getDeclaredConstructor(Class.java:2170)
    at androidx.work.WorkerFactory.createWorkerWithDefaultFallback(WorkerFactory.java:95)
    at androidx.work.impl.WorkerWrapper.runWorker(WorkerWrapper.java:243)
    at androidx.work.impl.WorkerWrapper.run(WorkerWrapper.java:145)
    at androidx.work.impl.utils.SerialExecutorImpl$Task.run(SerialExecutorImpl.java:96)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
    at java.lang.Thread.run(Thread.java:919)
Could not create Worker com.app.MyWorker

Code of Conduct

  • I agree to follow this project's Code of Conduct
@nan-li
Copy link
Contributor

nan-li commented Mar 24, 2023

Hi @sebastinto, I am so sorry for the delayed response and thank you for the detailed post.

We made a speculative fix for a WorkManager crash we have been seeing which is the one you linked to, but it shouldn't be interfering with code outside of our own work requests. However, these Android libraries could be more closely related and we may interfere unintentionally. I believe it may be related to your app using a custom Work Manager.

We will investigate the Android source code for Hilt and the Work module to see what may be happening, and perhaps be able to reproduce some part of this ourselves, such as the factory being null.

Can you answer some questions to help me understand what is happening?

  1. Did you ever experience WorkManager is not initialized properly crash before or now?
  2. How often does this java.lang.NoSuchMethodException crash happen? Any numerical data is appreciated. Is it a 100% of the time crash or rare?
  3. You are only seeing this in production and not able to reproduce it yourself, correct?

@sebastinto
Copy link
Author

@nan-li

We made a #1721 for a WorkManager crash we have been seeing which is the one you linked to, but it shouldn't be interfering with code outside of our own work requests. However, these Android libraries could be more closely related and we may interfere unintentionally. I believe it may be related to your app using a custom Work Manager.

I did skim through this fix since I thought might be the source of the crash but couldn't see anything obvious either. If it is what's causing the issue I'd be curious to know how so please keep us posted.

  1. Did you ever experience WorkManager is not initialized properly crash before or now?

Not that I know of.

  1. How often does this java.lang.NoSuchMethodException crash happen? Any numerical data is appreciated. Is it a 100% of the time crash or rare?

100%

  1. You are only seeing this in production and not able to reproduce it yourself, correct?

It was actually caught during QA before sending to prod (on a Google Play Store internal testing track) and I'm able to reproduce myself on release variants signed with a debug certificate.

@nan-li
Copy link
Contributor

nan-li commented Mar 27, 2023

Hi @sebastinto, thank you for the information.

I have some followup asks:

  1. I have not used Hilt before, and can try to reproduce, but it would be helpful if you have a project you could share reproducing the issue.
  2. Can you provide a full stack trace of all threads when the crash happens: java.lang.NoSuchMethodException: com.app.MyWorker.<init> [class android.content.Context, class androidx.work.WorkerParameters]

You can also email the project and/or stack traces to support@onesignal.com and reference this GitHub issue, if it is easier to send that way.

Thank you so much

@sebastinto
Copy link
Author

@nan-li

  1. I cannot share the project where I encountered this bug unfortunately but I'll see what I can do re: a sample project
  2. for the stack trace, what I posted was the entire thing. Here it is below with the tags included if that can help. If you have any suggestion on how to capture more data, please let me know! Thanks!
01:08:27.228 WM-WorkerFactory                     E  Could not instantiate com.myapp.MyWorker
                                                     java.lang.NoSuchMethodException: com.myapp.MyWorker.<init> [class android.content.Context, class androidx.work.WorkerParameters]
                                                     	at java.lang.Class.getConstructor0(Class.java:2332)
                                                     	at java.lang.Class.getDeclaredConstructor(Class.java:2170)
                                                     	at androidx.work.WorkerFactory.createWorkerWithDefaultFallback(WorkerFactory.java:95)
                                                     	at androidx.work.impl.WorkerWrapper.runWorker(WorkerWrapper.java:243)
                                                     	at androidx.work.impl.WorkerWrapper.run(WorkerWrapper.java:145)
                                                     	at androidx.work.impl.utils.SerialExecutorImpl$Task.run(SerialExecutorImpl.java:96)
                                                     	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
                                                     	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
                                                     	at java.lang.Thread.run(Thread.java:919)
01:08:27.228 WM-WorkerWrapper                     E  Could not create Worker com.myapp.MyWorker

@nan-li
Copy link
Contributor

nan-li commented Mar 28, 2023

Hi @sebastinto

  1. A minimum reproducible project with Hilt included will be very helpful, thank you so much. This will also help us investigate for (2) below.
  2. Sometimes the crash information will provide information about other threads like this comment.

@sebastinto
Copy link
Author

@nan-li

Here's a sample project for your consideration: https://github.com/sebastinto/OneSignalSampleProject

It is a pretty accurate reduced-complexity representation of the project where the issue occurred.

Unfortunately, I could not reproduce the exact same error BUT another error follows the same pattern of occurring with version 4.8.5 of the SDK but not 4.8.4 so maybe it will provide some additional insight and hopefully not complicate the debugging process.

I've added todo comments to the project to guide testing but here are some detailed steps:

  1. add a key in OneSignalExample.kt
  2. build a release version of the app and run it
  3. tap the START button

Look at the logcat to verify that ExampleCoroutineWorker fails throwing an error like the one below (which is different than the one I opened this issue with)

WM-WorkerWrapper        com.example.onesignalsampleproject   E  Work [ id=3d45d163-07a1-4d89-ba24-9c7da07070ce, tags={ com.example.onesignalsampleproject.work.ExampleCoroutineWorker, example_coroutine_worker_tag } ] failed because it threw an exception/error
                                                                                                    java.util.concurrent.ExecutionException: kotlin.UninitializedPropertyAccessException: lateinit property notificationManager has not been initialized
                                                                                                    	at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:516)
                                                                                                    	at androidx.work.impl.utils.futures.AbstractFuture.get(AbstractFuture.java:475)
                                                                                                    	at androidx.work.impl.WorkerWrapper$2.run(WorkerWrapper.java:317)
                                                                                                    	at androidx.work.impl.utils.SerialExecutorImpl$Task.run(SerialExecutorImpl.java:96)
                                                                                                    	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
                                                                                                    	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
                                                                                                    	at java.lang.Thread.run(Thread.java:1012)
                                                                                                    Caused by: kotlin.UninitializedPropertyAccessException: lateinit property notificationManager has not been initialized
                                                                                                    	at com.example.onesignalsampleproject.work.BaseWorker.getNotificationManager(BaseWorker.kt:30)
                                                                                                    	at com.example.onesignalsampleproject.work.BaseWorker.createNotificationChannel(BaseWorker.kt:71)
                                                                                                    	at com.example.onesignalsampleproject.work.BaseWorker.createForegroundInfo(BaseWorker.kt:42)
                                                                                                    	at com.example.onesignalsampleproject.work.ExampleCoroutineWorker.doWork(ExampleCoroutineWorker.kt:20)
                                                                                                    	at androidx.work.CoroutineWorker$startWork$1.invokeSuspend(CoroutineWorker.kt:68)
                                                                                                    	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
                                                                                                    	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
                                                                                                    	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
                                                                                                    	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
                                                                                                    	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
                                                                                                    	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)
WM-WorkerWrapper        com.example.onesignalsampleproject   I  Worker result FAILURE for Work [ id=3d45d163-07a1-4d89-ba24-9c7da07070ce, tags={ com.example.onesignalsampleproject.work.ExampleCoroutineWorker, example_coroutine_worker_tag } ]

Then:

Both workers should succeed.

As an additional troubleshooting step, since the field injection of NotificationManager seems to be the source of the error here, you can also test not using Hilt in BaseWorker.kt. Just follow the steps outlined in the todo comment.

Don't hesitate to follow-up with additional questions / clarifications, thanks!

@nan-li
Copy link
Contributor

nan-li commented Apr 4, 2023

Hi @sebastinto,

An update that I am able to run your project and receive the same error of java.util.concurrent.ExecutionException: kotlin.UninitializedPropertyAccessException: lateinit property notificationManager has not been initialized, and that changing the NotificationManager injection stops the error.

Nothing else of substance to update at this time.

@sebastinto
Copy link
Author

Thanks for the update @nan-li !

@isles1217
Copy link

isles1217 commented May 26, 2023

Is there any fix incoming for this issue? I recently had to drop our OneSignal version from 4.8.6 -> 4.8.4 after spending hours figuring out why a WorkManager task was failing.
Hilt version: 2.45
Hilt-work version: 1.0.0
Work Manager version: 2.8.1

@Aiuspaktyn
Copy link

Aiuspaktyn commented Aug 5, 2023

ANR with 4.8.4 too:

ANR in xx.xx.xx
                                                                                                    PID: 29518
                                                                                                    Reason: executing service xx.xx.xx/com.onesignal.SyncJobService
                                                                                                    ErrorId: e2a3f010-5014-427d-ac7e-b33fc84c9c5c
                                                                                                    Frozen: false

@ehsannewsuk
Copy link

Hi, is this issue is fixed in the latest version?

@rvenky125
Copy link

fixed?

@sebastinto
Copy link
Author

sebastinto commented Apr 23, 2024

fixed?

@rvenky125 I haven't experienced this particular issue on v5 so migrating will probably the way to solve it.

Unfortunately be advised that for our use case, #1854 and #1969 prevent us from migrating ATM but YMMV.

@kez-lab
Copy link

kez-lab commented May 4, 2024

me too

submit my code

@HiltAndroidApp
class HMHApplication : Application(), Configuration.Provider {
    @Inject
    lateinit var workerFactory: HiltWorkerFactory

    override val workManagerConfiguration: Configuration = Configuration.Builder()
        .setWorkerFactory(workerFactory)
        .build()
        
# Manifest
        <provider
            android:name="androidx.startup.InitializationProvider"
            android:authorities="${applicationId}.androidx-startup"
            android:exported="false"
            tools:node="merge">
            <!-- If you are using androidx.startup to initialize other components -->
            <meta-data
                android:name="androidx.work.impl.WorkManagerInitializer"
                android:value="androidx.startup"
                tools:node="remove" />
        </provider>

Error Log

FATAL EXCEPTION: main
                                                                                                    Process: com.hmh.sample, PID: 26481
                                                                                                    java.lang.RuntimeException: Unable to instantiate application com.hmh.sample.HMHApplication package com.hmh.sample: kotlin.UninitializedPropertyAccessException: lateinit property workerFactory has not been initialized
                                                                                                    	at android.app.LoadedApk.makeApplicationInner(LoadedApk.java:1520)
                                                                                                    	at android.app.LoadedApk.makeApplicationInner(LoadedApk.java:1449)
                                                                                                    	at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7625)
                                                                                                    	at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0)
                                                                                                    	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2478)
                                                                                                    	at android.os.Handler.dispatchMessage(Handler.java:106)
                                                                                                    	at android.os.Looper.loopOnce(Looper.java:230)
                                                                                                    	at android.os.Looper.loop(Looper.java:319)
                                                                                                    	at android.app.ActivityThread.main(ActivityThread.java:8893)
                                                                                                    	at java.lang.reflect.Method.invoke(Native Method)
                                                                                                    	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:608)
                                                                                                    	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103)
                                                                                                    Caused by: kotlin.UninitializedPropertyAccessException: lateinit property workerFactory has not been initialized
                                                                                                    	at com.hmh.sample.HMHApplication.getWorkerFactory(HMHApplication.kt:13)
                                                                                                    	at com.hmh.sample.HMHApplication.<init>(HMHApplication.kt:16)
                                                                                                    	at java.lang.Class.newInstance(Native Method)
                                                                                                    	at android.app.AppComponentFactory.instantiateApplication(AppComponentFactory.java:76)
                                                                                                    	at androidx.core.app.CoreComponentFactory.instantiateApplication(CoreComponentFactory.java:51)
                                                                                                    	at android.app.Instrumentation.newApplication(Instrumentation.java:1282)
                                                                                                    	at android.app.LoadedApk.makeApplicationInner(LoadedApk.java:1512)
                                                                                                    	at android.app.LoadedApk.makeApplicationInner(LoadedApk.java:1449) 
                                                                                                    	at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7625) 
                                                                                                    	at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0) 
                                                                                                    	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2478) 
                                                                                                    	at android.os.Handler.dispatchMessage(Handler.java:106) 
                                                                                                    	at android.os.Looper.loopOnce(Looper.java:230) 
                                                                                                    	at android.os.Looper.loop(Looper.java:319) 
                                                                                                    	at android.app.ActivityThread.main(ActivityThread.java:8893) 
                                                                                                    	at java.lang.reflect.Method.invoke(Native Method) 
                                                                                                    	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:608) 
                                                                                                    	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103) 

@lemeshkevich
Copy link

@nan-li To support Hilt injection in Workers we have to provide WorkManger with a custom configuration. To do that our Application class implements Configuration.Provider so that WorkManager could get the custom configuration having the application context. But OneSignal SDK breaks this and passes a default configuration to WorkManager ignoring our custom configuration.
The issue comes from OSWorkManagerHelper:
WorkManager.initialize(context, (new Configuration.Builder()).build());

Here is the documentation about the custom configuration: https://developer.android.com/develop/background-work/background-tasks/persistent/configuration/custom-configuration

@nan-li
Copy link
Contributor

nan-li commented May 9, 2024

Hi @lemeshkevich, thanks for your post, it makes sense to me.

The goal with initializing WorkManager ourselves is if we detect there is not one and we are about to use it (we have seen this in rare crashes when it should be available).

However, it may be that how Hilt works, it does not make the WorkManager instance available at the time we check but supplies it when it is called.

I will take a look at this issue again.

@isles1217
Copy link

I started getting a similar but different bug in OneSignal 5.1.14. I read through the explanations here and in this issue tracker thread. Different stack trace, but this is related to OneSignal instantiating a WorkManager instance without reading the application's Configuration, which, is presumably accessible via:
(context as? Configuration.Provider)?.workManagerConfiguration

I'm really not sure why they didn't bother to even attempt to read the application's configuration in #2052 instead of just using an empty, default configuration.

Stacktrace:

Could not instantiate com.example.app.workers.SomeWorker
java.lang.NoSuchMethodException: com.example.app.workers.SomeWorker.<init> [class android.content.Context, class androidx.work.WorkerParameters]
	at java.lang.Class.getConstructor0(Class.java:3395)
	at java.lang.Class.getDeclaredConstructor(Class.java:3077)
	at androidx.work.WorkerFactory.createWorkerWithDefaultFallback(WorkerFactory.java:94)
	at androidx.work.impl.WorkerWrapper.runWorker(WorkerWrapper.java:243)
	at androidx.work.impl.WorkerWrapper.run(WorkerWrapper.java:144)
	at androidx.work.impl.utils.SerialExecutorImpl$Task.run(SerialExecutorImpl.java:96)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
	at java.lang.Thread.run(Thread.java:1012)
Could not create Worker com.example.app.workers.SomeWorker

This workaround feels really stupid, but I was able to get around this issue by simply getting an instance of WorkManager in my application's onCreate() method, before OneSignal gets instantiated (and tries to access my application's WorkManager instance).

    override fun onCreate() {
        super.onCreate()
        WorkManager.getInstance(this)
        (...)

I was able to RGR test this just now. For anyone else running into this issue (or something similar like mine), maybe try this? This should ensure that WorkManager is available with the correct configuration before OneSignal calls it.

@mpjmarshall
Copy link

@isles1217 , this issue came up for us yesterday morning. It's kind of crazy that you posted a workaround just hours later. Thanks for this. It's solving our problem temporarily until OneSignal makes a fix.

@nan-li
Copy link
Contributor

nan-li commented Jun 20, 2024

Hi all, we made updates to Work Manager in several release, the latest of which are: Release 5.1.15 for the User Model SDK and Release 4.8.10 for the Player Model SDK.

Thanks for your help in resolving this.

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

Successfully merging a pull request may close this issue.

10 participants