You don't want LoggedInComponent and LoggedOutComponent to have differing network behavior, and you don't necessarily need network behavior to be consistent across the lifetime of LoggedInComponent or LoggedOutComponent, so it doesn't make sense for you to have them bind NetworkModule separately. I think it makes sense to make them available through AppComponent. Remember that not everything in AppComponent needs to have ApplicationScope: You can make unscoped bindings in AppComponent, which instructs Dagger to re-fetch or re-create the requested object every time.
Specifically, I would bind NetworkModule into a subcomponent of AppComponent, which you can recreate every time the NetworkModule configuration changes. Not only would this let you encapsulate some of the network details (see "subcomponents for encapsulation" in the Dagger User's Guide), but it would also let you take advantage of dependency injection throughout your network bindings if that's what you're looking for.
To ensure that you're getting the correct current NetworkComponent, you could create a NetworkManager that is singleton-bound (ApplicationScope). You can use that to hold the latest NetworkComponent.
@Module(subcomponents={NetworkComponent.class})
public abstract class ApplicationModule {
/** Unscoped. Fetch this fresh from NetworkComponentHolder each time. */
@Provides Retrofit provideRetrofit(NetworkManager manager) {
return manager.getNetworkComponent().getRetrofit();
}
}
@ApplicationScope public class NetworkManager {
private final Provider<NetworkComponent.Builder> builderProvider;
private NetworkComponent currentComponent;
@Inject public NetworkComponentHolder(
Provider<NetworkComponent.Builder> builderProvider) {
this.builderProvider = builderProvider;
currentComponent = builderProvider.get()
.withNetworkModule(getDefault())
.build();
}
public void updateSettings(String baseUrl) {
currentComponent = builderProvider.get()
.withNetworkModule(new NetworkModule(baseUrl))
.build();
}
public NetworkComponent getNetworkComponent() {
return currentComponent;
}
}
With this, most of your code in AppComponent, LoggedInComponent, and LoggedOutComponent can just inject a Retrofit
(or Provider<Retrofit>
) whenever they need to make a request. When a response comes back that tells you to update your base URL, you can inject a NetworkManager, call updateSettings
, and suddenly new requests for Retrofit will return your new instance. (Note however that old instances to Retrofit may still stick around, but you'll have that problem any time you're changing a dependency belonging to an existing instance.)
p.s. If NetworkModule is lightweight enough, or has consistent-enough bindings, you might opt to put NetworkModule directly onto ApplicationComponent and simply have NetworkManager hold the current Retrofit instance etc. You'll have to make that judgment call based on the number of bindings you want to pass through as provideRetrofit
does, compared to the number of bindings you'd want to encapsulate or hide away in a subcomponent.