google / dagger

A fast dependency injector for Android and Java.
https://dagger.dev
Apache License 2.0
17.42k stars 2.01k forks source link

Inject into Workers (androidx.WorkManager API) #1183

Closed droidrcc closed 4 years ago

droidrcc commented 6 years ago

Hi, from latest Google I/O you just presented the new androidx.work.Worker class, part of the new WorkManager API.

Since the Worker class is created by the framework (we only pass the Worker class type to the WorkManager), how can we @inject fields into the Worker ? Do you intend to add a new AndroidInjection.inject() function that takes a worker as argument ?

Thanks

bopbi commented 6 years ago

in my case i inject it using inside doWork()

if (applicationContext is MyApp) {
(applicationContext as MyApp).userComponent.inject(this)

....
}

*MyApp is the application class name for my android app

ronshapiro commented 6 years ago

We hope to have something soon. It likely will be in a new artifact (dagger-android-work), plus support for @ContributesAndroidInjector.

autonomousapps commented 6 years ago

I know this is the wrong repo for this comment, but I wonder aloud why they couldn't have created a WorkerFactory class similar to ViewModelProvider.Factory for defining how to create Worker objects. Or something. I just really don't understand why Android framework classes can never have useful public constructors.

andrew-veresov commented 6 years ago

Hope they'll add it soon

kuno commented 6 years ago

@ronshapiro

hi there, do you have the roughly release date for this dagger-android-work feature ?

ronshapiro commented 6 years ago

There are a few things that need to change, let me document them here:

  1. [ ] Add @ContributesAndroidInjector support
  2. [ ] Add an AndroidWorkInjection.inject() method, and probably also a map key.
  3. [ ] Figure out a good way to integrate both for DaggerApplication. The androidx effort is fantastic in that it splits up a lot of things into individual libraries, but it complicates the approach we've taken thus far with DaggerApplication. Specifically, someone could want a DaggerApplication that has androidx.work support but not androidx.fragment support, and we shouldn't drag in any of those dependencies if we don't need to. This is reliant on the first two items, but also requires some thought about how to support the different flavors of android injection that we want/may want in the future. Codegen could help here too, but it may not be the best option.

We also probably want to wait until the library is at least in beta, or at least we would have to release an "alpha" version of dagger-work.

Lastly, we need to modify our bazel configuration to pull in the android-work aars, hopefully that won't be too hard.

ferrerojosh commented 6 years ago

I made a gist of how I injected workers, its mostly the same as how other components are injected

https://gist.github.com/ferrerojosh/82bd92748f315155fa6a842f4ed64c82

Lots of boilerplate though

samybeng commented 6 years ago

Hello guys,

Thanks to @ferrerojosh !

I made a Java version for that in case some people need it. https://gist.github.com/ThePredators/1702e79c5d3860f415c542da446420eb

something15525 commented 5 years ago

Any updates on this now that androidx is released?

ronshapiro commented 5 years ago

The Workers API is still in alpha

On Wed, Oct 3, 2018 at 1:03 AM Michael Limb notifications@github.com wrote:

Any updates on this now that androidx is released?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/google/dagger/issues/1183#issuecomment-426445967, or mute the thread https://github.com/notifications/unsubscribe-auth/AAwY3bgljVQVtVUcoHRw4FvNwWdv4vMbks5ug-LDgaJpZM4T-q7x .

something15525 commented 5 years ago

Ah, good point.

usmanrana07 commented 5 years ago

I got throw a solution that's working in rather simple way. How good is that? Is there any pros/cons of it over the above discussed solutions (though above discussed ones didn't work for me). Answer: https://stackoverflow.com/a/52199645/5110536

And seems like WorkManager-alpha10 has support of DI , is it true?

sariego commented 5 years ago

WorkManager-alpha9 introduced WorkerFactory, which would allow you to use constructor injection with Dagger :D WorkManager-alpha10 introduced breaking changes, one of which is

The interface WorkerFactory and the concrete implementation DefaultWorkerFactory have been merged into an abstract class called WorkerFactory

source

CDRussell commented 5 years ago

Using WorkerFactory certainly seems like the right way going forward. I wrote up more of an explanation on how it is actually used - https://stackoverflow.com/a/53377279/1654145

luanmm commented 5 years ago

Thanks to @CDRussell and @ferrerojosh contributions, I was able to use DI in workers. The final code is a mix between the ideas exposed here - https://gist.github.com/luanmm/85dd8217ed3f7384e6bab075a8ab7a61.

I'm expecting, in the future, to be able to remove all of this custom code and see something like that directly in Dagger for Android. It would be the ideal scenario IMHO.

something15525 commented 5 years ago

Hey all,

Looks like WorkManager was released to beta today: https://medium.com/androiddevelopers/introducing-workmanager-2083bcfc4712

From what I've read here: https://developer.android.com/jetpack/docs/release-notes#december_19_2018, it says: This release contains no API changes; moving forward, WorkManager is expected to stay API stable until the next version unless there is a critical problem.

Does this mean no Dagger support is planning on being added? I realize this is probably not the ideal place to ask this question, but don't know where else to ask.

ronshapiro commented 5 years ago

That's good. We still have some other dagger.android architecture cleanups to get done before we can get this, but I hope soon. We've been spending most of our efforts on buildperf recently

something15525 commented 5 years ago

@ronshapiro okay, good to know it will be gotten to eventually. Thanks for the update!

arunkumar9t2 commented 5 years ago

@ronshapiro I just finished setting up constructor injection using multibinding for Workers using WorkerFactory.

Given the possibility of constructor injection, should members injection via AndroidInjection.inject(this) still be considered/encouraged?

I wrote about my implementation here. Full working sample here.

JakeWharton commented 5 years ago

I'd say members injection is always inferior to constructor injection. Fragments can be constructor injected as of 1.1+. Activities on 28+.

On Thu, Dec 20, 2018 at 7:56 PM Arunkumar notifications@github.com wrote:

@ronshapiro https://github.com/ronshapiro I just finished setting up constructor injection using multibinding for Workers using WorkerFactory https://developer.android.com/reference/androidx/work/WorkerFactory.

Given the possibility of constructor injection, should members injection via AndroidInjection.inject(this) still be considered/encouraged?

I wrote about my implementation here https://www.arunkumarsampath.in/dagger-recipes-illustrative-step-by-step-guide-to-achieve-constructor-injection-in-workmanager/. Full working sample here https://github.com/arunkumar9t2/dagger-workmanager.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/google/dagger/issues/1183#issuecomment-449193878, or mute the thread https://github.com/notifications/unsubscribe-auth/AAEEEWV4NPwCAOW9xAoKCVla_BgaBd-wks5u7DHIgaJpZM4T-q7x .

ghost commented 5 years ago

Thank to Jake's presentation

We now able to perform constructor injection, like so:

class HelloWorldWorker @AssistedInject constructor(
    @Assisted private val params: WorkerParameters,
    private val appContext: Context,
    private val foo: Foo
) : Worker(appContext, params) {
    private val TAG = "HelloWorldWorker"
    override fun doWork(): Result {
        Log.d(TAG, "Hello world!")
        Log.d(TAG, "Injected foo: $foo")
        return Result.success()
    }

    @AssistedInject.Factory
    interface Factory  : ChildWorkerFactory<HelloWorldWorker>
}

No subcomponent needed

Fully working sample can be found here (also step by step guide in README) https://github.com/raiytu4/dagger-workmanager

Ethan1983 commented 5 years ago

@raiytu4 If going with AssistedInjection, it would be nice to have a @WorkerInject similar to @InflationInject to avoid this layers of factories. Ideally, it would be nice for WorkManager library to provide these and that goes to what Jake was talking about Dagger providing customization for libraries to provide modules.

chrisbanes commented 5 years ago

I've (finally) just migrated to WM v1.0.0-beta01. Thanks to @raiytu4 for the nice example.

You can simplify it even further by just using the Context which you're given in the Factory. I did that in: https://github.com/chrisbanes/tivi/pull/254

arunkumar9t2 commented 5 years ago

Seems AssistedInject is easier compared to my subcomponent + multibinds approach.

Any reason the Context could be anything other than the Application context? I thought this was a reasonable assumption to make, so ended up providing it from my root app component. The only param known at runtime was the workerParameters.

nlgtuankiet commented 5 years ago

@arunkumar9t2 You can dive into the WorkManager source code to find out, from what I see is that the Context parameter is always your Application context.

@chrisbanes I checked out your pull, nice improvement you got there (remove the generic T), thank

interface ChildWorkerFactory {
    fun create(context: Context, params: WorkerParameters): ListenableWorker
}

But, I don't get the part

You can simplify it even further by just using the Context which you're given in the Factory.

About the context, I basically do the same thing as you did in the beginning, then I realize that the context is just our application context. And the point is, we only need "assisted" for parameters that can only be known at runtime. So there is no point to "assisted" the context.

chrisbanes commented 5 years ago

I disagree, I prefer to use whatever the factory passes me (if possible)

Ethan1983 commented 5 years ago

@chrisbanes Any reason not to have TiviWorkerFactory as singleton?

iamjoin7 commented 5 years ago

I've (finally) just migrated to WM v1.0.0-beta01. Thanks to @raiytu4 for the nice example.

You can simplify it even further by just using the Context which you're given in the Factory. I did that in: chrisbanes/tivi#254

Hey @chrisbanes. Thank you a lot of the example. I've been dealing with a few hours to set my WorkManager up like yours and I've done so but I'm stuck at a very weird bug. Would you have any idea on why this would happen?

When I do: workManager.enqueue(wWorkRequest)

The Work runs (doWork() is called)

but when I do: workManager.enqueueUniqueWork("Name", ExistingWorkPolicy.APPEND, wWorkRequest)

the work doesn't get start (doWork() is never called).

Prior to all my changes today such as AppWorkerFactory, ChildWorkerFactory, and all the changes you did in https://github.com/chrisbanes/tivi/pull/254, my enqueueUniqueWork used to work. So now injecting to the Worker classes via the way you've shown, somehow enqueueUniqueWork doesn't work. Only enqueue works.

Any help is appreciated! Thank you for your or anyone's time who can help out.

chrisbanes commented 5 years ago

@iamjoin7 Weird. Have you stepped through the WorkerFactory to see if its createWorker() is called?

iamjoin7 commented 5 years ago

@chrisbanes yes I had. createWorker() actually does not get called.

Although, after inspecting it more, this may be a WorkManager issue. I've checked the db with stetho and it seems the works are actually being saved, but never "run"ed

https://issuetracker.google.com/issues/121436946

It's quite weird how enqueueUniqueWork was working injecting via AndroidWorkerInjector.inject(this) but it's not via this @AssistedInject.Factory 🤔

Thank you for your time though @chrisbanes. I'm leaving my comment up in case someone else faces a simiar issue.

nlgtuankiet commented 5 years ago

@iamjoin7 I tested it out, work fine on my part though, could you provide more detail? https://github.com/nlgtuankiet/dagger-workmanager/blob/iamjoin7-issue/app/src/main/java/com/sample/daggerworkmanagersample/MainActivity.kt

iamjoin7 commented 5 years ago

Thank you for the exclusive branch @nlgtuankiet :)

I've copy pasted your HelloWorldWorker class and your sample code in my MainActivity, and it worked 🤔.

So after messing around the last 15 mins. I tried running your HelloWorldWorker with my MyWorker class's TAG, via: enqueueUniqueWork(MyWorker.TAG, ExistingWorkPolicy.APPEND... and again, HelloWorldWorker's doWork() never got called.

So somehow the "queue" for uniqueWorkName MyWorker.TAG got stuck(?) and it since I've had previous works already in queue, the new ones I added via enqueueUniqueWork where waiting for the first one to run.

So I went to my app's "App Info" and clicked "Clear Storage", emptying the databases. And ran my code again, and both HelloWorldWorker and MyWorker's doWork() got called.

So just to recap, the problem was the uniqueWorkName MyWorker.TAG was frozen (not sure how that was possible). I'll be ignoring the issue now as I suspect(am hoping) it is one-time thing.

Apologies for taking your time @chrisbanes and @nlgtuankiet, really appreciate your help.

vincent-paing commented 5 years ago

The worker api is stable now, will something like @ContributesAndroidInjector be introduced in Dagger

arunkumar9t2 commented 5 years ago

There are already two working approaches discussed in this thread and both involve constructor injection instead of members injection. One is by using subcomponents and multibindings (link) and other one is using Square's AssistedInject (link).

IMHO, I don't think a @ContributesAndroidInjector and AndroidInjection.inject(this) solution is warranted anymore but I agree a working solution with simpler setup would be nice.

DavidJRobertson commented 5 years ago

The fact that the above solutions require changing the configuration of the (singleton) WorkManager instance makes them unusable in libraries.

jega-ms commented 4 years ago

here is example for using worker and dagger with help of @ContributesAndroidInjector.

https://github.com/jega-ms/android-dagger2-mvp-rx/tree/master/app/src/main/java/com/jega/android/example/dagger2/worker

https://github.com/jega-ms/android-dagger2-mvp-rx/tree/master/app/src/main/java/dagger/worker

hoanglm4 commented 4 years ago

+1

heitorpr commented 4 years ago

+1

hoangdat commented 4 years ago

@nlgtuankiet can you share me a example of test for your worker with TestListenableWorkerBuilder in this way?. Many thanks!!!

nlgtuankiet commented 4 years ago

@hoangdat https://proandroiddev.com/dagger-2-setup-with-workmanager-a-complete-step-by-step-guild-bb9f474bde37?source=friends_link&sk=f616f5361147403a793fa16fb378e071

hoangdat commented 4 years ago

https://proandroiddev.com/dagger-2-setup-with-workmanager-a-complete-step-by-step-guild-bb9f474bde37?source=friends_link&sk=f616f5361147403a793fa16fb378e071

Srr, I did not find any test suite on it.

Zhuinden commented 4 years ago

Given the possibility of constructor injection, should members injection via AndroidInjection.inject(this) still be considered/encouraged?

It's possible to use AndroidInjection.inject(this) (sort of) over Workers since 2.20, where they removed the limitation that AndroidInjector was only generated for Activity/Fragment/Service/ContentProvider/BroadcastReceiver.

Now you can do it as simple as

    @ContributesAndroidInjector
    abstract fun syncWorker(): SyncWorker

and

    @Keep
    class SyncWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
        init {
            val injector = context.applicationContext as HasAndroidInjector
            injector.androidInjector().inject(this)
        }

        @Inject
        lateinit var apiService: ApiService
Chang-Eric commented 4 years ago

This should be covered now with the Jetpack Hilt extension (https://developer.android.com/training/dependency-injection/hilt-jetpack#workmanager). Unfortunately for dagger.android, we likely won't be adding WorkManager support directly for that so I'm going to close this.