1

I have my NetworkDataSource which I want to inject. I want to use constructor injection because I'm the owner of this class:

 public class NetworkDataSource {

    @Inject
    public NetworkDataSource(String url, String method) {
        this.url = url;
        this.method = method;
    }
 }

The thing is that the arguments of this constructor (which are the dependencies) are retrieved from a Service as extras when a BroadcastIntent is triggered. So my question is, how could I provide these arguments retrieved from the Service to my NetworkDataSource constructor injection?

The Service looks like this:

public class Service extends IntentService {

   // I would like to inject NetworkDataSource
   //@Inject
   //NetworkDataSource netWorkDataSource;

   public String url;
   public String method;

   @Override
   protected void onHandleIntent(Intent workIntent) {
     url = workIntent.getStringExtra("url");
     method = workIntent.getStringExtra("method");
   }

   ............
   ............

   networkDataSource.myMethod();
}

Thank you.

Erik Medina
  • 335
  • 5
  • 11
  • What should happen when `onHandleIntent` gets called a second time (and subsequent times)? – arekolek Oct 17 '18 at 16:01
  • @arekolek nothing special, I mean, `onHandleIntent` initialize some fields and calls some methods. One of those methods is `networkDataSource.myMethod()`. But if `onHandleIntent` is called more times, just has to do the same (it's the expected behavior). – Erik Medina Oct 17 '18 at 16:08

2 Answers2

2

It's a tricky question because you'd think Dagger primarily solves the problem that now you no longer need to introduce "Factory" classes in order to re-configure behavior.

However, you actually still need to use "Factory" classes to create objects with dynamic constructor arguments that are obtained at runtime.

So:

 public class NetworkDataSource {

    @Inject
    public NetworkDataSource(String url, String method) {
        this.url = url;
        this.method = method;
    }
 }

Would be

@Singleton
public class NetworkDataSourceFactory {
    private final OkHttpClient okHttpClient;
    private final Gson gson;

    @Inject
    public NetworkDataSourceFactory(OkHttpClient okHttpClient, Gson gson) {
        this.okHttpClient = okHttpClient;
        this.gson = gson;
    }

    public NetworkDataSource create(String url, String method) {
        return new NetworkDataSource(okHttpClient, gson, url, method);
    }
}

Then now you can inject NetworkDataSourceFactory, and create a NetworkDataSource when you receive your arguments:

@Inject NetworkDataSourceFactory factory;

....
    networkDataSource = factory.create(url, method);

Supposedly you can use AutoFactory to help with creating the factory, but I haven't used it yet, all I know is that it exists for this particular usecase.

EDIT: you can also check out https://github.com/square/AssistedInject to help with this problem.

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • 1
    Thank you, I didn't know about this issue with Dagger. Now I know that the solution you have raised is called **Assisted Injection**. To clarify more in this concept, there is a response from Jake Wharton in this post https://stackoverflow.com/a/16040942/3710314 – Erik Medina Oct 18 '18 at 10:21
1

Let's do it step by step. (All code will be in kotlin)

First of all, you have

class NetworkDataSource(val url: String, val method: String)

and need dagger module, which is responsible to create it

@Module
class NetworkModule {
    @Provides
    fun dataSource(@Named("url") url: String, 
                   @Named("method") method: String): NetworkDataSource {
        return NetworkDataSource(url, method)
    }
}

after that you need dagger component

@Component(modules = [NetworkModule::class])
interface NetworkComponent {
    fun inject(service: Service)
    @Component.Builder
    interface Builder {
        @BindsInstance
        fun url(@Named("url") url: String): Builder
        @BindsInstance
        fun method(@Named("method") method: String): Builder
        fun build(): NetworkComponent
    }
}

Now you can inject it inside Service

class Service : IntentService {
    @Inject
    lateinit var netWorkDataSource: NetworkDataSource
    override protected fun onHandleIntent(workIntent: Intent) {
        val url = workIntent.getStringExtra("url")
        val method = workIntent.getStringExtra("method")
        DaggerNetworkComponent.builder()
            .url(url)
            .method(method)
            .build()
            .inject(this)
    }
}

That's it.

ConstOrVar
  • 2,025
  • 1
  • 11
  • 14
  • You could just add `@Named` to both constructor parameters and get rid of the `NetworkModule` completely. – arekolek Oct 17 '18 at 22:22
  • @arekolek, yes, you are right. But in that case you also need to provide that `@Named` arguments by `Dagger`. For simplisity I described the whole process working with `Dagger` and injecting it. If author already has some `Dagger component`, he can use it instead of `NetworkComponent` and `NetworkModule`. – ConstOrVar Oct 18 '18 at 06:30
  • I do not understand what you mean by "you also need to provide that @Named arguments by Dagger" – arekolek Oct 18 '18 at 07:38