Dagger 2 injecting multiple instances of same obje

2019-02-11 22:37发布

问题:

Background

I am converting my app to MVP architecture and found Dagger 2 to be useful to inject dependencies when needed. My app needs to communicate with two web apis (my own and a third party api). There may be times where requests to my own api and the third party api could fire at the same time. I am using Retrofit to communicate with these apis and using GSON for serialistation / deserialisation.

What I did before

I created two Retrofit RestAdapters and used Service Locator pattern to obtain them when needed. The RestAdapter intended to be used for my own api includes GSONConverter with some custom TypeAdapters since I do not want 1:1 JSON deserialisation of my response in the app. The other RestAdapter intended for third party api and uses another GSONConverter with a specific field naming policy.

Problem

I am trying to use DI instead of Service Locator to obtain my RestAdapter (and the API interface). I have my NetModule class setup like follows

@Module
public class NetModule {

    private static final String MY_API_URL = "my_api_url";
    private static final String THIRD_PARTY_API_URL = "third_party_api_url";

    @Provides
    @Singleton
    Cache provideOkHttpCache(Application application) {
        int cacheSize = 10 * 1024 * 1024; // 10 MiB
        return new Cache(application.getCacheDir(), cacheSize);
    }

    @Provides
    @Singleton
    OkHttpClient provideOkHttpClient(Cache cache) {
        OkHttpClient client = new OkHttpClient();
        client.setCache(cache);
        return client;
    }

    @Provides
    @Singleton
    TypeAdapter<MyClass> provideMyAPITypeAdapter() {
        return new TypeAdapter<MyClass>() {
            // implementation ignored
        };
    }

    @Provides
    @Named("myApiGson")
    Gson provideGsonForMyAPI(TypeAdapter<MyClass> adapter) {
        return new GsonBuilder()
                .registerTypeAdapter(MyClass.class, adapter)
                .setDateFormat("yyyy-MM-dd HH:mm:ss")
                .create();
    }

    @Provides
    @Named("thirdPartyApiGson")
    Gson provideGsonForThirdPartyAPI() {
        return new GsonBuilder()
                .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
                .create();
    }

    @Provides
    @Named("myApiRestAdapter")
    RestAdapter provideMyRestAdapter(Gson gson, OkHttpClient okHttpClient) {
       return new RestAdapter.Builder()
                .setEndpoint(MY_API_URL)
                .setConverter(new GsonConverter(gson))
                .setClient(new OkClient(okHttpClient))
                .build();
    }

    @Provides
    @Named("thirdPartyApiRestAdapter")
    RestAdapter provideThirdPartyRestAdapter(Gson gson, OkHttpClient okHttpClient) {
       return new RestAdapter.Builder()
                .setEndpoint(THIRD_PARTY_API_URL)
                .setConverter(new GsonConverter(gson))
                .setClient(new OkClient(okHttpClient))
                .build();
    }

    @Provides
    @Singleton
    MyAPI provideMyAPI(RestAdapter adapter){
        return adapter.create(MyAPI.class);
    }

    @Provides
    @Singleton
    ThirdPartyAPI provideThirdPartyAPI(RestAdapter adapter){
        return adapter.create(ThirdPartyAPI.class);
    }
}

As you can see above in the code, the NetModule has methods to return two Gson objects and two RestAdapter objects. My questions are;

  1. How do I make sure that the correct dependencies are injected when creating specific RestAdapter & API Interfaces? (provideMyRestAdapter() requires GSON returned from provideGsonForMyAPI() and provideMyAPI() requires RestAdapter returned from provideMyRestAdapter().)

  2. How can I make sure that only two instances of RestAdapter (One for my api and other for third party api) are ever created during the lifetime of the application since creating RestAdapter is considered to be expensive. I am using @Named attribute on methods returning RestAdapters. Say for example when injecting dependency directly to field like this: @Inject("myApiRestAdapter") RestAdapter myRestadapter; is Dagger 2 going to create new RestAdapter every time or is it going to use one created before (like @Singleton but for specific object)?

I have just started using Dagger 2 and my understanding of how to use it may still be incorrect. Please correct me if I am doing something wrong here. Thanks for bearing up with this long question.

回答1:

You are already halfway through the solution. To complete the solution try to do the following:

@Provides
@Named("myApiRestAdapter")
RestAdapter provideMyRestAdapter(@Named("myApiGson") Gson gson, OkHttpClient okHttpClient) {
   return new RestAdapter.Builder()
            .setEndpoint(MY_API_URL)
            .setConverter(new GsonConverter(gson))
            .setClient(new OkClient(okHttpClient))
            .build();
}

@Provides
@Named("thirdPartyApiRestAdapter")
RestAdapter provideThirdPartyRestAdapter(@Named("thirdPartyApiGson") Gson gson, OkHttpClient okHttpClient) {
   return new RestAdapter.Builder()
            .setEndpoint(THIRD_PARTY_API_URL)
            .setConverter(new GsonConverter(gson))
            .setClient(new OkClient(okHttpClient))
            .build();
}

To make sure that only two instances of your RestAdapters are created during the lifetime of the application, annotate both the methods providing RestAdapter with @Singleton like you have done with your other methods. As for your other question whether Dagger 2 will create new instance of RestAdapter every time it has to inject it, I think it does this exactly, but I'm not sure on this.

Hope this helps!



回答2:

I saw this thread after posting my answer to a similar question. I wanted to provide a link because I think the same approach could be useful depending on your situation. It may be overkill for this exact question but I wanted to share in case it helps someone else.

https://stackoverflow.com/a/52348744/5046784

In short you can create unique interfaces / classes for each named object(e.g. MyApiGson and ThirdPartyApiGson) and then create @Provides for those rather than the generic Gson class. This way you can inject the instances by class/interface rather than a magic string name that you need to lookup or remember. Its a little more work but it helps when you have a bunch of independent modules that provide difference instances of the same Class.