Search code examples
javadagger-2android-workmanager

Inject classes into workmanager with dagger2 (java)


the trouble is that I can't to inject classes with dagger2 into worker (WorkManager) with java.

I've tried to understand the explanation of it here: https://proandroiddev.com/dagger-2-setup-with-workmanager-a-complete-step-by-step-guild-bb9f474bde37 And I don't know why - but in my case, it is won't work.

public class SimpleWorker extends androidx.work.Worker {

private String TAG = "SimpleWorker";

SomeModel someModel; // this is injected model

public SimpleWorker(@NonNull Context context, @NonNull WorkerParameters 
    workerParams) {
    super(context, workerParams);
}

@NonNull
@Override
public Result doWork() {
    Log.d(TAG, someModel.toString()); // but here always null
    return Result.success(); }
}

I wanted it works well!

YES - it is a repeat of my question WorkManager Java Android Dagger2 but it closed by moderators and I did not have time to answer it. I really want to save some time for other people. ps. Pls - don't delete it.


Solution

  • And there is the answer for people like me:

    Here it is Worker class:

    import android.content.Context;
    import android.support.annotation.NonNull;
    import android.util.Log;
    
    import androidx.work.ListenableWorker;
    import androidx.work.WorkerParameters;
    
    import javax.inject.Inject;
    import javax.inject.Provider;
    
    import com.sampleapp.model.Model;
    
    public class SimpleWorker extends androidx.work.Worker {
    
    //dagger (what we want to Inject into worker) U CAN ADD WHATEVER NEEDED
    private Model model;
    
    //not dagger (just some fields)
    private String someField;
    private final String TAG = getClass().getSimpleName();
    
    private SimpleWorker(@NonNull Context context,
                         @NonNull WorkerParameters workerParams,
                         Model model) {
        super(context, workerParams);
        this.model = model;
    
        someField = "just some work";
    }
    
    @NonNull
    @Override
    public ListenableWorker.Result doWork() {
        Log.d(TAG, "Worker starts");
        Log.d(TAG, model.getClass().getSimpleName() + " doing some work");
        Log.d(TAG, "Job done!");
        return ListenableWorker.Result.success();
    }
    
    public static class Factory implements ChildWorkerFactory {
    
        private final Provider<Model> modelProvider;
    
        @Inject
        public Factory(Provider<Model> modelProvider) {
            this.modelProvider = modelProvider;
        }
    
        @Override
        public ListenableWorker create(Context context, WorkerParameters workerParameters) {
            return new SimpleWorker(context,
                    workerParameters,
                    modelProvider.get());
        }
    }
    }
    

    Interface is:

    public interface ChildWorkerFactory {
    ListenableWorker create(Context appContext, WorkerParameters workerParameters);
    }
    

    WorkerFactory:

    import android.content.Context;
    import android.support.annotation.NonNull;
    import android.support.annotation.Nullable;
    
    import java.util.Map;
    
    import javax.inject.Inject;
    import javax.inject.Provider;
    
    import androidx.work.ListenableWorker;
    import androidx.work.WorkerFactory;
    import androidx.work.WorkerParameters;
    
    import com.sampleapp.model.Model;
    import com.sampleapp.model.CollectionsUtil;
    public class SimpleWorkerFactory extends WorkerFactory {
    
    private final Map<Class<? extends ListenableWorker>, Provider<ChildWorkerFactory>> workersFactories;
    
    @Inject
    public SimpleWorkerFactory(Map<Class<? extends ListenableWorker>, Provider<ChildWorkerFactory>> workersFactories) {
        this.workersFactories = workersFactories;
    }
    
    @Nullable
    @Override
    public ListenableWorker createWorker(@NonNull Context appContext, @NonNull String workerClassName, @NonNull WorkerParameters workerParameters) {
        Provider<ChildWorkerFactory> factoryProvider = CollectionsUtil.getWorkerFactoryProviderByKey(workersFactories, workerClassName);
        return factoryProvider.get().create(appContext, workerParameters);
    }
    }
    

    CollectionUtils:

    /**
     *
     * @param map workers
     * @param key workers name (class name)
     * @return
     */
    public static Provider<ChildWorkerFactory> getWorkerFactoryProviderByKey(Map<Class<? extends ListenableWorker>, Provider<ChildWorkerFactory>> map, String key) {
        for (Map.Entry<Class<? extends ListenableWorker>, Provider<ChildWorkerFactory>> entry : map.entrySet()) {
            if (Objects.equals(key, entry.getKey().getName())) {
                return entry.getValue();
            }
        }
        return null;
    }
    

    Worker binding:

    import dagger.Binds;
    import dagger.Module;
    import dagger.multibindings.IntoMap;
    
    @Module
    public interface WorkerBindingModule {
        @Binds
        @IntoMap
        @WorkerKey(SimpleWorker.class)
        ChildWorkerFactory bindHelloWorldWorker(SimpleWorker.Factory factory);
    }
    

    WorkerKey annotation:

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import androidx.work.ListenableWorker;
    import dagger.MapKey;
    
    @MapKey
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface WorkerKey {
        Class<? extends ListenableWorker> value();
    }
    

    Part of Application class:

    private static AppComponent component;
    
    private void configureWorkManager() {
        UpdaterWorkerFactory factory = component.factory();
        Configuration config = new Configuration.Builder()
                .setWorkerFactory(factory)
                .build();
    
        WorkManager.initialize(this, config);
    }
    

    Part of AppComponent interface:

    @Singleton
    @Component(modules = {AppModule.class, WorkerBindingModule.class})
    public interface AppComponent {
        // Some other injects here
        SimpleWorkerFactory factory();
    }
    

    And part of manifest (inside of Application):

    <provider
            android:name="androidx.work.impl.WorkManagerInitializer"
            android:authorities="${applicationId}.workmanager-init"
            android:exported="false"
            tools:node="remove"/>
    

    What was in gradle:

    // (Java only)
    implementation ("android.arch.work:work-runtime:1.0.1")
    

    ps. And IF it will get some conflicts with firebase

    api 'com.google.guava:guava:27.1-android'
    

    Notice: In my case Model was injected throw Interface. like:

    public class ModelImplementation implements Model {
    private ModelImplementation() {
        App.getComponent().inject(this);
    }
    }
    

    In the same AppComponent!

    to use this amazing feature just use something like (in Activity for example):

    PeriodicWorkRequest periodicWorkRequest = new PeriodicWorkRequest.Builder(SimpleWorker.class, 
    Const.WORKER_PERIOD, TimeUnit.MINUTES).build();
        WorkManager.getInstance().enqueue(periodicWorkRequest);
    

    pps. Const.WORKER_PERIOD - a period in minutes (min 15)

    targetSDK is 27