This repository was archived by the owner on Aug 20, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 44
Fill in README.md #12
Open
TWiStErRob
wants to merge
4
commits into
JakeWharton:master
Choose a base branch
from
TWiStErRob:usage
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,16 +3,133 @@ Dagger Reflect | |
|
|
||
| A reflection-based implementation of the [Dagger][dagger] dependency injection library for fast IDE builds. | ||
|
|
||
| More info soon... | ||
| See [my talk (from about 37:25)][talk] for more details on how this came about. | ||
|
|
||
|
|
||
| Er, what? Why would I want this? | ||
| -------------------------------- | ||
|
|
||
| Feels like going back to Dagger 1 by Square? Not quite: we keep the benefits of Dagger 2, including the annotation processing compile-time validation in production, but using reflection speeds up development builds. | ||
|
|
||
| The normal `dagger` artifact requires the use of `dagger-compiler` as an annotation processor for compile-time validation of your components and code generation for runtime performance. | ||
| This is a desirable feature for your CI and release builds, but it slows down iterative development. | ||
| By using `dagger-reflect` for only your IDE builds, you have one less annotation processor sitting between you and your running app. This is especially important for Kotlin-only or Java/Kotlin mixed projects using KAPT. And if `dagger-compiler` is your only annotation | ||
| processor for a module, using `dagger-reflect` means that **zero** annotation processors run during development. | ||
|
|
||
|
|
||
| Can I use this in production? | ||
| ----------------------------- | ||
|
|
||
| No. | ||
|
|
||
| Well technically you _can_, but don't. It's slow, inefficient, and lacks the level of validation that normal Dagger usage provides. | ||
|
|
||
|
|
||
| Usage | ||
| ----- | ||
|
|
||
| Details soon... | ||
| _Replace (or fulfill via gradle.properties) `${VERSION_DAGGER}` and `${VERSION_DAGGER_REFLECT}` with whatever versions you are using._ | ||
|
|
||
| If you have a dedicated variant for development you can skip the `if` check and add the corresponding dependencies for the development and non-development variants. | ||
|
|
||
| ### Easy migration, quick APT | ||
| This usage type is compatible with the current dagger usage patterns where users of Dagger call into the generated code via `DaggerFooComponent.create()` or `DaggerFooComponent.builder()`. The `reflect-compiler` generates source compatible code as `dagger-compiler` would, which resolves the components via reflection. The drawback is that there's still an annotation processor present, albeit much faster than Dagger. | ||
|
|
||
| ```gradle | ||
| dependencies { | ||
| implementation "com.google.dagger:dagger:${VERSION_DAGGER}" | ||
| if (project.hasProperty('android.injected.invoked.from.ide')) { | ||
| implementation "com.jakewharton.dagger:dagger-reflect:${VERSION_DAGGER_REFLECT}" | ||
| annotationProcessor "com.jakewharton.dagger:dagger-reflect-compiler:${VERSION_DAGGER_REFLECT}" | ||
| } else { | ||
| annotationProcessor "com.google.dagger:dagger-compiler:${VERSION_DAGGER}" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| _(No need to upgrade Proguard shrinking configuration with this approach.)_ | ||
|
|
||
| ### Pure reflection, no APT | ||
| This usage type replaces everything with reflection, but requires a few trivial code changes across the app. | ||
|
|
||
| ```gradle | ||
| dependencies { | ||
| implementation "com.google.dagger:dagger:${VERSION_DAGGER}" | ||
| if (project.hasProperty('android.injected.invoked.from.ide')) { | ||
| implementation "com.jakewharton.dagger:dagger-reflect:${VERSION_DAGGER_REFLECT}" | ||
| } else { | ||
| implementation "com.jakewharton.dagger:dagger-codegen:${VERSION_DAGGER_REFLECT}" | ||
| annotationProcessor "com.google.dagger:dagger-compiler:${VERSION_DAGGER}" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| To bridge the compatibility between `dagger-compile` generated classes and `dagger-reflect`, you'll have to use `dagger.Dagger` class (from `dagger-codegen` or `dagger-reflect`) as a replacement: | ||
| * `FooComponent foo = DaggerFooComponent.create()` | ||
| → `FooComponent foo = Dagger.create(FooComponent.class)` | ||
| * `FooComponent foo = DaggerFooComponent.builder().….build()` | ||
| → `FooComponent foo = Dagger.builder(FooComponent.Builder.class).….build()` | ||
|
|
||
| For the release build using `dagger-codegen` and shirking you'll need to add this to the configuration: | ||
| ```proguard | ||
| # [dagger-reflect] Make sure to keep entry points that dagger.Dagger reflectively accesses | ||
| # fixes: Unable to find generated component implementation com.example.Daggera for component com.example.a | ||
|
|
||
| # Keep annotation to be able to match it below | ||
| -keep class dagger.Component | ||
| # Keep the names of the Component interfaces so we can prefix them with "Dagger" | ||
| -keepnames @dagger.Component interface ** | ||
| # Keep the names of the generated Component implementations so the prefixed lookup succeeds | ||
| -keepnames class ** implements @dagger.Component ** | ||
| # Keep the builder() method in the generated Component to reflectively call it | ||
| -keepclassmembers class ** implements @dagger.Component ** { | ||
| public static ** builder(); | ||
| } | ||
| # Keep the create() method in the generated Component to reflectively call it | ||
| -keepclassmembers class ** implements @dagger.Component ** { | ||
| public static <2> create(); | ||
| } | ||
| ``` | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These rules can be embedded in the jar to be picked up automatically and we can add test cases to ensure they work. They don't need to be documented anywhere. |
||
|
|
||
| ### SNAPSHOT | ||
|
|
||
| Snapshots of the development version are available in [Sonatype's `snapshots` repository][snap]. | ||
| Don't forget to suffix `VERSION_DAGGER_REFLECT` above with `-SNAPSHOT`, the rest of the usage is the same. | ||
|
|
||
| ```gradle | ||
| repositories { | ||
| maven { name = "Sonatype SNAPSHOTs"; url = "https://oss.sonatype.org/content/repositories/snapshots/" } | ||
| } | ||
| ``` | ||
|
|
||
|
|
||
| Limitations | ||
| ----------- | ||
|
|
||
| * `@Component`s and `@Component.Builder`s must be interfaces | ||
| While Dagger allows `abstract class`es for these, `dagger-reflect` uses Java Proxies to handle invocations which cannot extend classes at runtime. | ||
|
|
||
| * `@Component` interfaces must be public (default visible won't work): | ||
| This is due to a limitation in Java, where instances of proxies cannot create another proxy instance where the second interface is not public. This prevents proxies of builders from creating proxies of the component. See `dagger.reflect.ComponentBuilderInvocationHandler.create`. | ||
|
|
||
| * There are some missing features that are not yet implemented. | ||
| They can be found by looking at [`notImplemented` calls in the code][notImplemented]. | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There won't be any releases until these are all gone |
||
| (Beware: GitHub shows only the first few occurrences in each file, not all of them.) | ||
|
|
||
| Compatibility | ||
| ------------- | ||
|
|
||
| * ProGuard/DexGuard/R8: since the dependency injection entry point (e.g. `@Component.Builder`) is being reflected with either usage case, you'll lose some shrinkability, but the majority of the generated `@Component` code using `@Module`s will be shrinked the same as before. | ||
|
|
||
| * `dagger-android`: it uses a lot of Dagger features, namingly: | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Most of these are implementation details. I don't think it's our responsibility or useful to document all this. |
||
| * `@Multibinds` in `AndroidInjectionModule` | ||
| * `@ContributesAndroidInjector` generates a `@Module` with a `@Subcomponent(modules = ...)` | ||
| * the generated module uses `@Binds @IntoMap @ClassKey` | ||
| * the generated subcomponent inherits a generic builder base | ||
| * the generated subcomponent inherits a generic `@BindsInstance` method | ||
| * the generated subcomponent's builder is an `abstract class` because of `dagger.android.AndroidInjector.Builder#create` | ||
|
|
||
| Sadly, this last one is a showstopper for `dagger-reflect` and to mitigate we'll need help from the core Dagger team. Alternative it may be possible to add direct support for `@ContributesAndroidInjector` in the future to `dagger-reflect`. | ||
|
|
||
|
|
||
| License | ||
|
|
@@ -33,6 +150,7 @@ License | |
| limitations under the License. | ||
|
|
||
|
|
||
|
|
||
| [dagger]: https://github.com/google/dagger/ | ||
| [snap]: https://oss.sonatype.org/content/repositories/snapshots/ | ||
| [talk]: https://jakewharton.com/helping-dagger-help-you/ | ||
| [notImplemented]: https://github.com/JakeWharton/dagger-reflect/search?q=%22throw%20notImplemented%22&type=Code | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dagger 1 used code generation and compile-time validation. The only feature it didn't have compared Dagger 2 was... maps?
This project is more like Guice.