Android Dagger2 + OkHttp + Retrofit dependency cycle error

后端 未结 4 1541
不知归路
不知归路 2020-12-08 11:15

Hey there I am using Dagger2, Retrofit and OkHttp and I am facing dependency cycle issue.

When providing OkHttp :

4条回答
  •  粉色の甜心
    2020-12-08 11:49

    Your problem is:

    1. Your OKHttpClient depends on your Authenticator
    2. Your Authenticator depends on a Retrofit Service
    3. Retrofit depends on an OKHttpClient (as in point 1)

    Hence the circular dependency.

    One possible solution here is for your TokenAuthenticator to depend on an APIServiceHolder rather than a APIService. Then your TokenAuthenticator can be provided as a dependency when configuring OKHttpClient regardless of whether the APIService (further down the object graph) has been instantiated or not.

    A very simple APIServiceHolder:

    public class APIServiceHolder {
    
        private APIService apiService;
    
        @Nullable
        APIService apiService() {
            return apiService;
        }
    
        void setAPIService(APIService apiService) {
            this.apiService = apiService;
        }
    }
    

    Then refactor your TokenAuthenticator:

    @Inject
    public TokenAuthenticator(@NonNull APIServiceHolder apiServiceHolder, @NonNull ImmediateSchedulerProvider schedulerProvider) {
        this.apiServiceHolder = apiServiceHolder;
        this.schedulerProvider = schedulerProvider;
        this.disposables = new CompositeDisposable();
    }
    
    @Override
    public  Request authenticate(Route route, Response response) throws IOException {
    
        if (apiServiceHolder.get() == null) {
             //we cannot answer the challenge as no token service is available
    
             return null //as per contract of Retrofit Authenticator interface for when unable to contest a challenge
        }    
    
        request = null;            
    
        TokenResponse tokenResponse = apiServiceHolder.get().blockingGet()
    
        if (tokenResponse.isSuccessful()) {
            saveUserToken(tokenResponse.body());
            request = response.request().newBuilder()
                         .header("Authorization", getUserAccessToken())
                         .build();
        } else {
           logoutUser();
        }
    
        return request;
    }
    

    Note that the code to retrieve the token should be synchronous. This is part of the contract of Authenticator. The code inside the Authenticator will run off the main thread.

    Of course you will need to write the @Provides methods for the same:

    @Provides
    @ApplicationScope
    apiServiceHolder() {
        return new APIServiceHolder();
    }
    

    And refactor the provider methods:

    @Provides
    @ApplicationScope
    APIService provideAPI(Retrofit retrofit, APIServiceHolder apiServiceHolder) {
        APIService apiService = retrofit.create(APIService.class);
        apiServiceHolder.setAPIService(apiService);
        return apiService;
    }
    

    Note that mutable global state is not usually a good idea. However, if you have your packages organised well you may be able to use access modifiers appropriately to avoid unintended usages of the holder.

提交回复
热议问题