Skip to content
This repository was archived by the owner on Aug 20, 2025. It is now read-only.
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 121 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Copy Markdown
Owner

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.


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();
}
```
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The 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].
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The 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:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The 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
Expand All @@ -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