From 159dc68c355c14add320e71071af292206e5e2ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B3bert=20Papp=20=28TWiStErRob=29?= Date: Mon, 4 Feb 2019 01:55:05 +0000 Subject: [PATCH 1/4] Fill in readme --- README.md | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 99 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 881f8a38..f55eac1a 100644 --- a/README.md +++ b/README.md @@ -3,16 +3,112 @@ 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 shirking 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(); +} +``` + +### 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/" } +} +``` + +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: Details soon... License @@ -33,6 +129,6 @@ 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/ From 84d95583cf200ed4962257fae5292e02c9bd4785 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Mon, 4 Feb 2019 11:56:13 +0000 Subject: [PATCH 2/4] Update README.md Co-Authored-By: TWiStErRob --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f55eac1a..1d002903 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ dependencies { } ``` -_(No need to upgrade Proguard shirking configuration with this approach.)_ +_(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. From 19b622307226067ff49d1d2769ecf2315e03ca35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B3bert=20Papp=20=28TWiStErRob=29?= Date: Tue, 5 Feb 2019 00:13:53 +0000 Subject: [PATCH 3/4] Add limitations (proxies, missing features), and describe dagger-android --- README.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1d002903..a4dd0f2d 100644 --- a/README.md +++ b/README.md @@ -103,12 +103,33 @@ repositories { ``` +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](https://github.com/JakeWharton/dagger-reflect/search?q=notImplemented&type=Code). + (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: Details soon... + * `dagger-android`: it uses a lot of Dagger features, namingly: + * `@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 From 7417d7c5353c1848744d81d3576be33e72095525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B3bert=20Papp=20=28TWiStErRob=29?= Date: Wed, 6 Feb 2019 22:01:39 +0000 Subject: [PATCH 4/4] Update URL to find more results by skipping imports of notImplemented --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a4dd0f2d..f7f031bc 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ Limitations 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](https://github.com/JakeWharton/dagger-reflect/search?q=notImplemented&type=Code). + They can be found by looking at [`notImplemented` calls in the code][notImplemented]. (Beware: GitHub shows only the first few occurrences in each file, not all of them.) Compatibility @@ -153,3 +153,4 @@ 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