Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IllegalStateException in BuildingGraphValidator (475) #123

Closed
Jearil opened this issue Feb 18, 2015 · 4 comments
Closed

IllegalStateException in BuildingGraphValidator (475) #123

Jearil opened this issue Feb 18, 2015 · 4 comments

Comments

@Jearil
Copy link

Jearil commented Feb 18, 2015

I'm attempting to compile my project and it's running into an IllegalStateException in the BuildingGraphValidator on line 475. There's no error message in that exception so I'm not really sure what's failing or why.

Looking at the code it looks like it has to do with validating the scope of the components. I do know that I have a component of scope A depending on a component of scope B (basically one is a singelton-level scope and the other is an activity-level scope). I'm going to guess that the issue is a misplaced or missing scope annotation, but the error message is blank so it doesn't give much of an indicator.

@Jearil
Copy link
Author

Jearil commented Feb 18, 2015

A basic example:

@Module
public class BaseModule {
  private Context context;
  public BaseModule(Context context) { this.context = context; }

  @Provides
  @ApplicationScope
  public Context providesContext() { return context; }
}

@Module
public class ContextModule {
  private Context context;
  public ContextModule(Context context) { this.context = context; }

  @Provides
  @ActivityScope
  public Context providesLocalContext() { return context; }

@ApplicationScope
@Component(modules = BaseModule.class)
public interface BaseComponent {
  @ApplicationScope
  public Context appContext();
}

@Component(dependencies = BaseComponent.class, modules = ContextModule.class)
public interface ActivityComponent {
  @ActivityScope
  public Context activityContext();
}

The idea for this is that some objects will use an application level context (mainly singletons like Volley) while others might need just the local context (Some business logic class in a fragment). Ideally I'd just be able to specify which I'd like via:

@Inject
public SomePresenter(@ActivityScope Context context) { ... }

or

@Inject
public VolleyHelper(@ApplicationScope Context context) { ... }

@cgruber
Copy link

cgruber commented Mar 25, 2015

Looking at your example, I think you're conflating two concepts - scope and qualification, and are doing so because Android is insane and uses the Context API for both application-level things and activity-level things.

I recommend you either supply @Qualifier annotations to disambiguate application context from activity context, since these are two different instances, and you want them to be two different bindings,

... or (my recommendation) just drop using Context as a type, and bind Application and Activity, since nearly every usage where you'd consume these will either need to know one from the other, or won't care and will behave correctly for any Context, and so you can feed those methods the appropriate context yourself.

Then you don't have a lot of @Foo Context things sprinkled around your code. You just have two types - Application and Activity - being consumed where appropriate, and being treated as a Context where relevant. It'll save you a world of hurt.

As to scope validation, I think your problem will likely go away if you use @Qualifier and @Scope correctly. Scope goes in one of three places:

When attached to the type of a component interface, that scope annotation represents a constraint such that all bindings reachable by that component (either via included modules, or implicitly via @Inject constructors must either be unscoped, or be of the same scope as the component. For instance the following means that FooActivity must be @ActivityScoped as must any objects owned by MyActivityComponent except where it gets them from a dependency (which is a different component, so a different constraint applies). Like this:

@ActivityScope
@Component(...) interface MyActivityComponent {
  FooActivity activity()
}

A scope annotation can also go on a @Provides method or on the type of an injectable object (an object with an @Inject constructor). Like so:

@ActivityScope
class MyFoo implements Foo {
  @Inject MyFoo(Bar bar) {...}
}

@Module class MyModule {
  @ActivityScope Bar bar(BarImpl impl) { return impl; }
}

This signals to dagger that these bindings in question are to be memoized in the component that was marked with @ActivityScope.

Scope in Dagger is far less automagical than in Guice or Spring. Lifetimes are determined by the lifetime of the component's instance - what you get from the component builder. @Scope annotations don't change that semantic very much, they merely are used as a signal to share an instance across a component, and are used to ensure that you don't declare something in one component that you meant to use in a component with a different lifetime.

@cgruber
Copy link

cgruber commented Mar 25, 2015

All that said, we shouldn't have illegal state exceptions, we should have proper compiler errors. Can you please provide a stack-trace so we can figure out what check we're letting slip through?

@cgruber
Copy link

cgruber commented Mar 26, 2015

We have had several updates since this but was filed - for now I'll close it. Feel free to re-open it if you can replicate the behavior with the latest snapshot.

@cgruber cgruber closed this as completed Mar 26, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants