Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass in parameters to a dagger module from a activity or fragment at runtime

My software specifications are as follows:

Android Studio 3.4
dagger-android 2.16

I have the following class that passes a MapboxGeocoder that will execute and return a response.

class GeocodingImp(private val mapboxGeocoder: MapboxGeocoder) : Geocoding {

    override fun getCoordinates(address: String, criteria: String): AddressCoordinate {
        val response = mapboxGeocoder.execute()

        return if(response.isSuccess && !response.body().features.isEmpty()) {
            AddressCoordinate(
                response.body().features[0].latitude,
                response.body().features[0].longitude)
        }
        else {
            AddressCoordinate(0.0, 0.0)
        }
    }
}

However, the MapboxGeocoder is generated in a dagger module at compile time. So I have to specify the string for the address and TYPE_ADDRESS.

@Reusable
@Named("address")
@Provides
fun provideAddress(): String = "the address to get coordinates from"

@Reusable
@Provides
@Named("geocoder_criteria")
fun provideGeocoderCriteria(): String = GeocoderCriteria.TYPE_ADDRESS

@Reusable
@Provides
fun provideMapboxGeocoder(@Named("address") address: String, @Named("geocoder_criteria") geocoderCriteria: String): MapboxGeocoder =
    MapboxGeocoder.Builder()
        .setAccessToken("api token")
        .setLocation(address)
        .setType(geocoderCriteria)
        .build()

@Reusable
@Provides
fun provideGeocoding(mapboxGeocoder: MapboxGeocoder): Geocoding =
    GeocodingImp(mapboxGeocoder)

my component class:

interface TMDispatchMobileUIComponent {
    @Component.Builder
    interface Builder {
        @BindsInstance
        fun application(application: TMDispatchMobileUIApplication): Builder

        fun build(): TMDispatchMobileUIComponent
    }

    fun inject(application: TMDispatchMobileUIApplication)
}

In the main activity I would use this like this as the user can enter in a different address or change the criteria to something else. But as the module are compiled I cannot pass any parameters to them at runtime:

presenter.getAddressCoordinates("this should be the actual address", GeocoderCriteria.TYPE_ADDRESS)

For my injection into the Activity I use the following:

AndroidInjection.inject(this)

Is there any solution to this problem?

like image 322
ant2009 Avatar asked Dec 28 '18 18:12

ant2009


People also ask

What is inject annotation in dagger?

With the @Inject annotation on the constructor, we instruct Dagger that an object of this class can be injected into other objects. Dagger automatically calls this constructor, if an instance of this class is requested.

What is @component in dagger?

Now Component in a Dagger works by creating a graph of all the dependencies in the project so that it can find out where it should get those dependencies when they are needed. In order to implement this, an interface needs to be created and should be annotated with @Component.

What is the difference between Dagger and hilt?

In Dagger, we create scope annotations such as ActivityScope, FragmentScope to specify the lifecycle, but hilt provides us with core components such as Application, Activity, Fragment, Service, and View.


2 Answers

The problem you have can be solved using "Assisted injection" approach.

It means that you need a class to be built both using dependencies provided from the existing scopes and some dependencies from the instance's creator, in this case, your main activity. Guice from Google has a nice description of what it is and why it is needed

Unfortunately, Dagger 2 does not have this feature out from the box. However, Jake Wharton is working on a separate library that can be attached to Dagger. Moreover, you can find more details in his talk on Droidcon London 2018, where he dedicated a whole talk section for this question: https://jakewharton.com/helping-dagger-help-you/

like image 133
Gaket Avatar answered Oct 14 '22 06:10

Gaket


You can recreate your whole component at runtime if you wish, where you'd then pass in the parameters to your module as a constructor parameter. Something like:

fun changeAddress(address: String) {
    val component = DaggerAppComponent.builder() //Assign this to wherever we want to keep a handle on the component
            .geoModule(GeoModule(address))
            .build()
    component.inject(this) //To reinject dependencies
}

And your module would look like:

@Module
class AppModule(private val address: String) {...}

This method may be wasteful though, if you're creating many different objects in your component.

like image 23
urgentx Avatar answered Oct 14 '22 07:10

urgentx