Skip to content

Commit

Permalink
Merge pull request #1 from IlyaGulya/feature/ksp
Browse files Browse the repository at this point in the history
Feature/ksp
  • Loading branch information
IlyaGulya authored May 25, 2024
2 parents 64cd1a7 + 9f6c9be commit f335572
Show file tree
Hide file tree
Showing 21 changed files with 834 additions and 129 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@
## [Unreleased]

### Added
- Support Anvil KSP mode

### Changed
- Updated `Anvil` to `2.5.0-beta09`
- Better support for incremental compilation using new `GeneratedFilesWithSources` API from `Anvil`
- Split `:samples` module to multi-module structure showcasing usage of KSP and non-KSP code generators. See `Module structure in the project` section in README.md for more details.
- Updated `README.md` with explanation on how to use KSP code generators.

### Deprecated

Expand All @@ -24,5 +29,5 @@



[Unreleased]: https://github.com/square/anvil/compare/v0.1.0...HEAD
[Unreleased]: https://github.com/IlyaGulya/anvil-utils/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/IlyaGulya/anvil-utils/releases/tag/v0.1.0
118 changes: 96 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,103 @@
# Anvil Utils

Anvil Utils is a library that provides a set of annotations to simplify the development of modular applications with Dagger and Square Anvil.
Anvil Utils is a library that provides a set of annotations to simplify the development of modular applications with
Dagger and Square Anvil.

## Features

- `@ContributesAssistedFactory` - automatically generates assisted factories for annotated classes and contributes them to the specified scope as bindings of the provided factory type.
- `@ContributesAssistedFactory` - automatically generates assisted factories for annotated classes and contributes them
to the specified scope as bindings of the provided factory type.

## Getting Started
## Compatibility Map

1. Add the following dependencies to your project:
| Anvil Utils Version | Anvil Version |
|---------------------|---------------|
| 0.1.0 | 2.4.2 |
| 0.2.0-beta01 | 2.5.0-beta09 |

```kotlin
dependencies {
implementation("me.gulya.anvil:annotations:0.1.0")
anvil("me.gulya.anvil:compiler:0.1.0")
}
```
## Getting Started

2. Enable Anvil in your project by applying the Anvil Gradle plugin:
### KSP

1. Follow Zac Sweer's [guide](https://www.zacsweers.dev/preparing-for-k2/#anvil) on how to prepare your project for
Anvil with KSP and K2.
2. Ensure you have at least `2.5.0-beta09` version of Anvil in your project.
3. Ensure you have `ksp` plugin applied to your project:
```kotlin
plugins {
id("com.google.devtools.ksp")
}
```

4. Add the following dependencies to your project:
```kotlin
dependencies {
implementation("me.gulya.anvil:annotations:0.1.0")
ksp("me.gulya.anvil:compiler:0.1.0")
}
```

5. Enable Anvil in your project by applying the Anvil Gradle plugin:
```kotlin
plugins {
id("com.squareup.anvil") version "2.5.0-beta09"
}
```

6. Enable KSP and Dagger factory generation in `Anvil`:
```kotlin
anvil {
useKsp(
contributesAndFactoryGeneration = true,
)
generateDaggerFactories = true
}
```

### Without KSP

```kotlin
plugins {
id("com.squareup.anvil") version "2.4.2"
}
```
1. Add the following dependencies to your project:
```kotlin
dependencies {
implementation("me.gulya.anvil:annotations:0.1.0")
anvil("me.gulya.anvil:compiler:0.1.0")
}
```
2. Enable Anvil in your project by applying the Anvil Gradle plugin:
```kotlin
plugins {
id("com.squareup.anvil") version "2.5.0-beta09"
}
```

3. Enable Dagger factory generation in `Anvil`:
```kotlin
anvil {
generateDaggerFactories = true
}
```

## @ContributesAssistedFactory

`@ContributesAssistedFactory` is an annotation that helps to automatically generate
assisted factories for annotated classes and contribute them to the specified scope
`@ContributesAssistedFactory` is an annotation that helps to automatically generate
assisted factories for annotated classes and contribute them to the specified scope
as bindings of the provided factory type.

### Motivation
When building modular applications with Dagger, it's common to define an API module with public interfaces
and a separate implementation module with concrete classes. Assisted injection is a useful pattern for creating

When building modular applications with Dagger, it's common to define an API module with public interfaces
and a separate implementation module with concrete classes. Assisted injection is a useful pattern for creating
instances of classes with a mix of dependencies provided by Dagger and runtime parameters.
However, using Dagger's @AssistedFactory requires the factory interface and the implementation
class to be in the same module, which breaks the separation between API and implementation.

`@ContributesAssistedFactory` solves this problem by allowing to declare the bound type (factory interface) in the
`@ContributesAssistedFactory` solves this problem by allowing to declare the bound type (factory interface) in the
API module and generate the actual factory implementation in the implementation module.

### Usage

1. Define your bound type (factory interface) in the API module:

```kotlin
interface MyClass

Expand All @@ -53,6 +107,7 @@ interface MyClassFactory {
```

2. Annotate your implementation class with `@ContributesAssistedFactory` in the implementation module:

```kotlin
@ContributesAssistedFactory(AppScope::class, MyClassFactory::class)
class DefaultMyClass @AssistedInject constructor(
Expand All @@ -62,6 +117,7 @@ class DefaultMyClass @AssistedInject constructor(
```

3. The following factory will be generated, implementing MyClassFactory:

```kotlin
@ContributesBinding(AppScope::class, MyClassFactory::class)
@AssistedFactory
Expand All @@ -70,5 +126,23 @@ interface DefaultMyClass_AssistedFactory : MyClassFactory {
}
```

### Module structure in the project

- `:compiler` - contains code generators
- Package `me.gulya.anvil.utils.ksp` - KSP code generators
- Package `me.gulya.anvil.utils.embedded` - non-KSP code generators
- `:annotations` - contains annotations supported by this code generator
- `:samples` - sample project with examples of usage
- `:samples:entrypoint` - entrypoint modules showcasing usage of KSP and non-KSP code generators.
- `:samples:embedded` - entrypoint module where component merging is done. Depends on non-KSP implementation
module.
- `:samples:ksp` - entrypoint module where component merging is done. Depends on KSP implementation module.
- `:samples:library` - library modules using this code generator.
- `:samples:library:api` - API module with factory interfaces.
- `:samples:library:impl:ksp` - Implementation module using KSP code generator.
- `:samples:library:impl:embedded` - Implementation module using non-KSP code generator.

### Important notes
- The factory interface method parameters should be annotated with @AssistedKey instead of Dagger's @Assisted because Dagger disallow such usage of this annotation.

- The factory interface method parameters should be annotated with @AssistedKey instead of Dagger's @Assisted because
Dagger disallow such usage of this annotation.
3 changes: 3 additions & 0 deletions compiler/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ dependencies {

implementation(libs.anvil.compiler.utils)
implementation(libs.kotlinpoet)
implementation(libs.kotlinpoet.ksp)
implementation(libs.dagger)

implementation(libs.ksp.api)

compileOnly(libs.google.autoservice.annotations)
kapt(libs.google.autoservice.compiler)

Expand Down
57 changes: 57 additions & 0 deletions compiler/src/main/kotlin/me/gulya/anvil/utils/Errors.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import me.gulya.anvil.assisted.AssistedKey

internal object Errors {
fun missingBoundType(className: String): String {
return "The @ContributesAssistedFactory annotation on class '$className' " +
"must have a 'boundType' parameter"
}

fun mustHaveSinglePrimaryConstructor(className: String): String {
return "Class '$className' annotated with @ContributesAssistedFactory " +
"must have a single primary constructor"
}

fun primaryConstructorMustBeAnnotatedWithAssistedInject(className: String): String {
return "Class '$className' annotated with @ContributesAssistedFactory " +
"must have its primary constructor annotated with @AssistedInject"
}

fun boundTypeMustBeAbstractOrInterface(boundTypeName: String, assistedFactoryName: String): String {
return "The bound type '$boundTypeName' for @ContributesAssistedFactory on class " +
"'$assistedFactoryName' must be an abstract class or interface"
}

fun boundTypeMustHasSingleAbstractMethod(boundType: String): String {
return "The bound type '$boundType' for @ContributesAssistedFactory " +
"must have a single abstract method"
}

fun parameterMismatch(boundTypeName: String, factoryMethodName: String, assistedFactoryName: String): String {
return "The assisted factory method parameters in '$boundTypeName.$factoryMethodName' " +
"must match the @Assisted parameters in the primary constructor of " +
"'$assistedFactoryName'"
}

fun parameterMustBeAnnotatedWithAssistedKey(
factoryParameterName: String,
boundTypeName: String,
factoryMethodName: String
): String {
return "The parameter '${factoryParameterName}' in the factory method " +
"'${boundTypeName}.${factoryMethodName}' must be annotated with " +
"@${AssistedKey::class.simpleName} instead of @${Assisted::class.simpleName} " +
"to avoid conflicts with Dagger's @${AssistedFactory::class.simpleName} annotation"
}

fun parameterDoesNotMatchAssistedParameter(factoryParameterName: String, assistedFactoryName: String): String {
return "The factory method parameter '${factoryParameterName}' does not match any @Assisted parameter " +
"in the primary constructor of '${assistedFactoryName}'"
}

fun boundTypeMustBeClassOrInterface(boundTypeName: String): String {
return "Bound type ${boundTypeName} must be a class or interface"
}

}
Loading

0 comments on commit f335572

Please sign in to comment.