Android-job: No virtual method setInitialDelay() with WorkManager 2.2.0

Created on 2 Oct 2019  路  14Comments  路  Source: evernote/android-job

Using the latest stable releases of android-job and WorkManager:

implementation 'com.evernote:android-job:1.4.1'
implementation 'androidx.work:work-runtime:2.2.0'

I get this crash when I try to schedule a job:

 Caused by: java.lang.NoSuchMethodError: No virtual method setInitialDelay(JLjava/util/concurrent/TimeUnit;)Landroidx/work/OneTimeWorkRequest$Builder; in class Landroidx/work/OneTimeWorkRequest$Builder; or its super classes (declaration of 'androidx.work.OneTimeWorkRequest$Builder' appears in /data/app/com.youneedabudget.evergreen.app.develop-Kx7Jp9uBEt29bXoJWoCEcw==/base.apk)
    at com.evernote.android.job.work.JobProxyWorkManager.plantOneOff(JobProxyWorkManager.java:50)
    at com.evernote.android.job.JobManager.scheduleWithApi(JobManager.java:244)
    at com.evernote.android.job.JobManager.schedule(JobManager.java:199)
    at com.evernote.android.job.JobRequest.schedule(JobRequest.java:430)

This is the first time I have tried to add the WorkManager dependency, so I don't know if it worked with older (or non-AndroidX) WorkManager versions. Previously I was just using android-job's default implementation without WorkManager, and that has always worked fine.

If I remove the WorkManager dependency then everything works.

I am using Jetifier.

bug

Most helpful comment

I was curious about the binary incompatibility, so took a closer look at that change between 2.0.1 and 2.2.0 - now I believe I've understood the issue and would like to share with the class.

Old:

abstract class WorkRequest {
    abstract static class Builder<B extends Builder, W extends WorkRequest> {
    }
}

class OneTimeWorkRequest extends WorkRequest {
   static class Builder extends WorkRequest.Builder<Builder, OneTimeWorkRequest> {
       public @NonNull Builder setInitialDelay(long duration, @NonNull TimeUnit timeUnit) {
          ....
       }
  }
}

New:

abstract class WorkRequest {
    abstract static class Builder<B extends Builder, W extends WorkRequest> {
         public @NonNull B setInitialDelay(long duration, @NonNull TimeUnit timeUnit) {
              ...
         }
    }
}

class OneTimeWorkRequest extends WorkRequest {
   static class Builder extends WorkRequest.Builder<Builder, OneTimeWorkRequest> {
  }
}

i.e., that method in question was pulled up into a super type. That's fine. But the signature changed: the return type was previously OneTimeWorkRequest.Builder. In the new version, at compile time, it is still known to be exactly that, when called with a OneTimeWorkRequest as receiver. But during compilation, due to type erasure, that type parameter B gets erased to its upper bound: WorkRequest.Builder. So in 2.2.0, there is really no virtual method setInitialDelay(JLjava/util/concurrent/TimeUnit;)Landroidx/work/OneTimeWorkRequest$Builder; to be found on type OneTimeWorkRequest$Builder; it is setInitialDelay(JLjava/util/concurrent/TimeUnit;)Landroidx/work/WorkRequest$Builder; now.

Thanks for making me learn today. https://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html was enlightening.

All 14 comments

Hm, not really sure what's going on, the method definitely exists: https://developer.android.com/reference/androidx/work/WorkRequest.Builder#setInitialDelay(long,%20java.util.concurrent.TimeUnit).

Are you sure that there isn't another issue? I bumped the version in the library and cannot reproduce this issue.

I'm seeing exactly the same issue. Since I'm right now in the process of bumping dependencies, and am doing so systematically and one-by-one, I can tell you that this problem

  • was (of course) not present with android-job 1.4.1 and no WorkManager on the classpath
  • and appeared the moment I added WorkManager to the classpath (exactly the same as OP).
    Nothing else changed.
    Happens both after updating the apk on the device (from 1.4.1 without workmanager to 1.4.1 with workmanager) and on a clean install.

And yes, I see the method existing; IDE navigates me there just fine from the decompiled OneTimeWorkRequest. This is bizarre. Something something binary incompatibility because of different version - I have no idea.

Sorry, forgot to mention: yes, I cleaned the build and retried it; same result.

Maybe a missing proguard rule?

Not related to Proguard, this happens in a debug build.

Can you reproduce the issue in a sample app? That would be really helpful.

Looks like the only thing that changed is https://android-review.googlesource.com/c/platform/frameworks/support/+/953484/

I am not sure if this actually qualifies as a binary incompatible change. Can you just switch to using 2.2 stable ?

Yes, I'll release new version depending on the latest stable WorkManager version. Thanks @tikurahul !

I've published a new version: https://github.com/evernote/android-job/releases/tag/v1.4.2 @GrahamBorland and @strooooke does the new version work for you?

Yes, works now, thanks!

I'm still not quite sure whether I actually want to use workmanager in production; those issues

I was curious about the binary incompatibility, so took a closer look at that change between 2.0.1 and 2.2.0 - now I believe I've understood the issue and would like to share with the class.

Old:

abstract class WorkRequest {
    abstract static class Builder<B extends Builder, W extends WorkRequest> {
    }
}

class OneTimeWorkRequest extends WorkRequest {
   static class Builder extends WorkRequest.Builder<Builder, OneTimeWorkRequest> {
       public @NonNull Builder setInitialDelay(long duration, @NonNull TimeUnit timeUnit) {
          ....
       }
  }
}

New:

abstract class WorkRequest {
    abstract static class Builder<B extends Builder, W extends WorkRequest> {
         public @NonNull B setInitialDelay(long duration, @NonNull TimeUnit timeUnit) {
              ...
         }
    }
}

class OneTimeWorkRequest extends WorkRequest {
   static class Builder extends WorkRequest.Builder<Builder, OneTimeWorkRequest> {
  }
}

i.e., that method in question was pulled up into a super type. That's fine. But the signature changed: the return type was previously OneTimeWorkRequest.Builder. In the new version, at compile time, it is still known to be exactly that, when called with a OneTimeWorkRequest as receiver. But during compilation, due to type erasure, that type parameter B gets erased to its upper bound: WorkRequest.Builder. So in 2.2.0, there is really no virtual method setInitialDelay(JLjava/util/concurrent/TimeUnit;)Landroidx/work/OneTimeWorkRequest$Builder; to be found on type OneTimeWorkRequest$Builder; it is setInitialDelay(JLjava/util/concurrent/TimeUnit;)Landroidx/work/WorkRequest$Builder; now.

Thanks for making me learn today. https://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html was enlightening.

Wow, great find. Thank you very much, I'll forward this to Google!

Working nicely now. Thanks @vRallev!

Thanks for confirming.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jsu4650 picture jsu4650  路  5Comments

deviant-studio picture deviant-studio  路  5Comments

ExploiTR picture ExploiTR  路  6Comments

karntrehan picture karntrehan  路  5Comments

SwiftyWang picture SwiftyWang  路  7Comments