3

I'm working on MVVM architecture and I'm using Dagger2 for data injection. The issue that i'm facing is that In Activity/Fragments the @Inject is working fine but in Worker class of WorkManager the @Inject gives null pointer exception at runtime. How can i resolve it?

Following is the Worker class code:

public class MySyncManager extends Worker {

    @Inject
    DataManager dataManager;

    @Inject
    SchedulerProvider schedulerProvider;

    @NonNull
    @Override
    public WorkerResult doWork() {

        CommonUtils.Log("usm_work_manager_1", "Work is Started.");
         if(dataManager==null) 
        CommonUtils.Log("usm_work_manager", "Injector is NULL");

    }
    }

method:

    private void startTestSyncRequest() {
        Constraints myConstraints = new Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .build();
        OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(MySyncManager.class)
                .setConstraints(myConstraints)
                .setInitialDelay(1, TimeUnit.SECONDS)  // wait for n seconds before starting service
                .build();

        WorkManager.getInstance()
                .beginUniqueWork(Tags.TEST_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest)
                .enqueue();

}
Bonzo
  • 427
  • 2
  • 10
  • 28
Usman Rana
  • 2,067
  • 1
  • 21
  • 32
  • 1
    Where do you call `myComponent.inject(mySyncManager)`? Please include the code where you inject your workers. – David Medenjak May 25 '18 at 12:24
  • @DavidMedenjak , i'm not injecting WorkManager and i'm just calling above method to startWorker, because it has some variable rules that may vary for different requests. – Usman Rana May 25 '18 at 12:49
  • If you don't inject the fields they will be `null`, so you have to inject it somehow somewhere before using it. You have to do this manually – David Medenjak May 25 '18 at 12:51
  • But the models that i'm trying to inject in Worker are added in AppModule and it works fine in other places i.e. activity or fragments. To me it seems thread specific issue, but i'm unsure. – Usman Rana May 25 '18 at 12:55

3 Answers3

5

To inject fields in worker class firstly you need to inject your worker class inside AppComponent :

 fun inject(syncItemWorker: SyncItemWorker)

then inside worker's doWork() , you need to call inject:

 if(applicationContext is MyApp){
        var daggerAppComponent= DaggerAppComponent.builder().application(applicationContext as MyApp).build()
        daggerAppComponent.inject(this)
    }
0

They still don't have internal support for WorkManager. It likely will be in a new artifact (dagger-android-work), plus support for @ContributesAndroidInjector.

But we can create our own everything to get it done. Follow below code.

AppComponent.java

    @Singleton
    @Component(modules = {//rest of your modules,
    AndroidWorkerInjectionModule.class,
    WorkerModule.class})
   public interface AppComponent {

@Component.Builder
interface Builder {

    @BindsInstance
    Builder application(Application application);

    AppComponent build();
}

void inject(App npp);

DataManager getDataManager();
}

AndroidWorkerInjectionModule.java

@Module
public abstract class AndroidWorkerInjectionModule {

@Multibinds
abstract Map<Class<? extends Worker>, AndroidInjector.Factory<? extends 
 Worker>>
workerInjectorFactories();
 }

WorkerModule.class

@Module(subcomponents = {
    WorkerComponent.class
     })
  public abstract class WorkerModule {
  @Binds
  @IntoMap
  @WorkerKey(CustomWorkManager.class)
  abstract AndroidInjector.Factory<? extends Worker> 
  bindProfileWorkerFactory(WorkerComponent.Builder profileWorker);
    }

WorkerComponent.class

@Subcomponent
public interface WorkerComponent extends AndroidInjector<CustomWorkManager> {
   //Here, CustomWorkManager is the class that extends Worker of WorkManager.You write your own class
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<CustomWorkManager>{}

}

HasWorkerInjector.class

public interface HasWorkerInjector {
AndroidInjector<Worker> workerInjector();
  }

AndroidWorkerInjection.class

public class AndroidWorkerInjection {

public static void inject(Worker worker) {
    //TODO : Check not null

    Object application = worker.getApplicationContext();

    if (!(application instanceof HasWorkerInjector)) {
        throw new RuntimeException(
                String.format(
                        "%s does not implement %s",
                        application.getClass().getCanonicalName(),
                        HasWorkerInjector.class.getCanonicalName()));
    }

    AndroidInjector<Worker> workerInjector =
            ((HasWorkerInjector) application).workerInjector();
    checkNotNull(workerInjector, "%s.workerInjector() returned null", 
   application.getClass());
    workerInjector.inject(worker);
  }
}

WorkerKey.class

@MapKey
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface WorkerKey {
Class<? extends Worker> value();
  }

Now you can inject all the dependency in CustomWorkManager. Happy Coding!

Abhishek Kumar
  • 4,532
  • 5
  • 31
  • 53
-1

Field injection is not recommended, considering constructor injection. Here is a good example of constructor injection, check this blog. Basically you could inject like:

@AssistedInject
public PrePopulateDataWorker(@Assisted @NonNull Context context, @Assisted @NonNull WorkerParameters workerParams, DataManager dataManager) {
    super(context, workerParams);
    this.dataManager = dataManager;
}

@AssistedInject.Factory
public interface Factory extends CustomWorkerFactory {}

Here the instance of DataManger is injected. Here is the Java implementation

Yanbin Hu
  • 537
  • 6
  • 18