0

I have a LoginActivity where the user logs in via Auth0 and returns an auth token. This token is passed to MainActivity:

Intent intent = new Intent(LoginActivity.this, MainActivity.class);
intent.putExtra(KEY_ACCESS_TOKEN, credentials.getAccessToken());
intent.putExtra(KEY_ID_TOKEN, credentials.getIdToken());
startActivity(intent);

I was able to get dependency injection working with LoginActivity fine by following this guide.

Now I'm trying to inject dependencies into MainActivity. My MainActivity has a MainActivityViewModel to handle all the interactions between the UI and the data layer. I'd like to inject my API into my ViewModel:

PetshackApi apiService;

@Inject
public PetMapViewModel(PetshackApi apiService) {
    this.apiService = apiService;
}

I have ViewModelModule, ViewModelKey, and MainActivityViewModelFactory (renamed from GithubViewModelFactory) defined. I injected the viewModelFactory at the top of my MainActivity:

@Inject
ViewModelProvider.Factory viewModelFactory;

And then use the factory to get my viewModel:

viewModel = ViewModelProviders.of(this, viewModelFactory).get(MainActivityViewModel.class);

I set this up using this answer.

The problem is that my Retrofit/PetshackApi dependency will require the accessToken from the LoginActivity. So I defined another method in my MainActivity to allow retrieving it:

public String getAccessToken() {
    return getIntent().getStringExtra(LoginActivity.KEY_ACCESS_TOKEN);
}

I'm having trouble setting up my modules/components/???. I think I need to inject MainActivity somehow into my modules so I tried following Injecting Activity objects.

MainActivityComponent.java

@Component(modules={AndroidSupportInjectionModule.class, AppModule.class, MainActivityModule.class, ViewModelModule.class})
public interface MainActivityComponent extends AndroidInjector<PetApplication> {
    @Component.Builder
    abstract class Builder extends AndroidInjector.Builder<PetApplication>{
        @BindsInstance
        abstract Builder application(Application application);
    }
    void inject(MainActivity mainActivity);
}

MainActivityModule.java

@Module(subcomponents = MainActivitySubcomponent.class)
abstract class MainActivityModule {
    @Binds
    @IntoMap
    @ActivityKey(MainActivity.class)
    abstract AndroidInjector.Factory<? extends Activity>
    bindMainActivityInjectorFactory(MainActivitySubcomponent.Builder builder);
}

MainActivitySubcomponent.java

@Subcomponent(modules = MainActivityChildModule.class)
public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
    @Subcomponent.Builder
    public abstract class Builder extends AndroidInjector.Builder<MainActivity> {}
}

MainActivityChildModule.java

@Module
abstract class MainActivityChildModule {
    @Provides
    @Singleton
    Retrofit providesRetrofit(Application application, MainActivity mainActivity) {
        final String accessToken = mainActivity.getAccessToken();
        Interceptor interceptor = new Interceptor() {
            @Override
            public okhttp3.Response intercept(Chain chain) throws IOException {
                Request newRequest = chain.request().newBuilder()
                        .addHeader("authorization", "Bearer " + accessToken).build();
                return chain.proceed(newRequest);
            }
        };

        // Add the interceptor to OkHttpClient
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.interceptors().add(interceptor);
        OkHttpClient client = builder.build();

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(application.getString(R.string.endpoint_url))
                .addConverterFactory(GsonConverterFactory.create())
                .client(client)
                .build();
        return retrofit;
    }

    @Provides
    @Singleton // needs to be consistent with the component scope
    PetshackApi providesPetshackApiInterface(Retrofit retrofit) {
        return retrofit.create(PetshackApi.class);
    }
}

Am I on the right track? Any hints or examples on how to do this?

Kit Menke
  • 7,046
  • 1
  • 32
  • 54

1 Answers1

1

I'd recommend moving your networking code outside of your Activity module and creating an Application module that could be shared across your application.

The important thing is, if you have a TokenStore that provides your token for each request you'd need to update the value as requests are sent.

@Module
abstract class NetworkModule {

    @Provides
    @Singleton
    static TokenStore provideTokenStore(TokenStoreImpl tokenStore) {
        return tokenStore;
    }

    @Provides
    @Singleton
    static OkHttpClient provideOkHttpClient(AuthInterceptor authInterceptor) {
        // Add the interceptor to OkHttpClient
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.interceptors().add(authInterceptor);
        return builder.build();
    }

    @Provides
    @Singleton
    static Retrofit providesRetrofit(Application application, OkHttpClient okHttpClient) {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(application.getString(R.string.endpoint_url))
                .addConverterFactory(GsonConverterFactory.create())
                .client(okHttpClient)
                .build();
        return retrofit;
    }

    @Provides
    @Singleton // needs to be consistent with the component scope
    static PetshackApi providesPetshackApiInterface(Retrofit retrofit) {
        return retrofit.create(PetshackApi.class);
    }
}

interface TokenStore {
    String getToken();
    void setToken(String token);
}

@Singleton
class TokenStoreImpl implements TokenStore {

    String token;

    @Inject
    public TokenStoreImpl() { }

    @Override
    public String getToken() {
        return token;
    }

    @Override
    public void setToken(String token) {
        this.token = token;
    }
}

@Singleton
class AuthInterceptor implements Interceptor {

    private final TokenStore tokenStore;

    @Inject
    public AuthInterceptor(TokenStore tokenStore) {
        this.tokenStore = tokenStore;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request newRequest = chain.request()
                .newBuilder().addHeader("authorization", "Bearer " + tokenStore.getToken()).build();
        return chain.proceed(newRequest);
    }
}
Chris
  • 2,332
  • 1
  • 14
  • 17
  • Thank you. This makes sense and gets me past requiring a dependency on MainActivity. I'm working on implementing what you have here. – Kit Menke Apr 10 '18 at 15:06