Skip to content
Merged
Show file tree
Hide file tree
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- Add support for incremental annotation processing.
### Fixed
- Fix sources not getting attached to some packages, now they should be visible from Android Studio.

Expand Down
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,31 @@ dependencies {
}
```

### Recommended settings
#### JDK8 requirements
Ensure that your project has compatibility with Java 8:
```groovy
android {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

kotlinOptions {
jvmTarget = "1.8"
}
}
```
#### Improve compilation speed
In order to speed up the compilation process, it is recommended to add the following settings in
your `gradle.properties`:
```groovy
## Improves kapt speed with parallel annotation processing tasks, may impact in memory usage
kapt.use.worker.api=true
## Enables Gradle build cache
org.gradle.caching=true
```

### \[Android] Setting up your App file

You'll need to add the following snippet to your `Application`'s `onCreate` method. If you don't have it, then create it and reference it in your `AndroidManifest.xml` file:
Expand Down
8 changes: 6 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,12 @@ android {
}

compileOptions {
sourceCompatibility "1.7"
targetCompatibility "1.7"
sourceCompatibility "1.8"
targetCompatibility "1.8"
}

kotlinOptions {
jvmTarget = "1.8"
}

lintOptions {
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/java/org/sample/SampleData.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,19 @@ interface ActionInterface {
val text: String
}

@Action
class ActionOne(override val text: String) : ActionInterface

@Action
class ActionTwo(override val text: String) : ActionInterface

data class DummyState(val text: String = "dummy")
class DummyStore : Store<DummyState>() {
@Reducer
fun onActionOne(action: ActionOne) {
newState = state.copy(text = "${state.text} ${action.text}")
}

@Reducer
fun onActionTwo(action: ActionTwo) {
newState = state.copy(text = action.text)
Expand Down
5 changes: 5 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ android.enableJetifier=true
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
kapt.use.worker.api=true
kapt.include.compile.classpath=false

org.gradle.caching=true
org.gradle.parallel=true
11 changes: 8 additions & 3 deletions mini-processor/build.gradle
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
apply plugin: 'kotlin'
apply plugin: 'kotlin-kapt'
apply from: "../jitpack.gradle"

dependencies {
api project(":mini-common")
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.github.yanex:takenoko:0.1'
implementation 'com.squareup:kotlinpoet:1.3.0'
implementation 'com.squareup:kotlinpoet:1.5.0'

// Lib to add incremental annotation processing
def incap_version = "0.2"
compileOnly "net.ltgt.gradle.incap:incap:$incap_version"
kapt "net.ltgt.gradle.incap:incap-processor:$incap_version"

testImplementation 'junit:junit:4.12'
testImplementation 'com.google.testing.compile:compile-testing:0.15'
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,34 +22,36 @@ object ActionTypesGenerator {

val prop = PropertySpec.builder("actionTypes", mapType)
.addModifiers(KModifier.PRIVATE)
//⇤⇥«»
.initializer(CodeBlock.builder()
.add("mapOf(\n⇥")
.addStatement("mapOf(")
.indent()
.apply {
actionModels.forEach { actionModel ->
val comma = if (actionModel != actionModels.last()) "," else ""
add("«")
add("«") // Starts statement
add("%T::class to ", actionModel.typeName)
add(actionModel.listOfSupertypesCodeBlock())
add(comma)
add("\n»")
add("\n")
add("»") // Ends statement
}
}
.add("⇤)")
.unindent()
.add(")")
.build())
addProperty(prop.build())
}.build()
}
}

class ActionModel(val element: Element) {
val type = element.asType()
val typeName = type.asTypeName()
val superTypes = collectTypes(type)
private class ActionModel(element: Element) {
private val type = element.asType()
private val superTypes = collectTypes(type)
.sortedBy { it.depth }
//Ignore base types
.filter { it.mirror.qualifiedName() != "java.lang.Object" }
.filter { it.mirror.qualifiedName() != "mini.BaseAction" }
// Ignore base types
.filter { it.mirror.qualifiedName() !in listOf("java.lang.Object", "mini.BaseAction") }
val typeName = type.asTypeName()


fun listOfSupertypesCodeBlock(): CodeBlock {
val format = superTypes.joinToString(",\n") { "%T::class" }
Expand All @@ -58,16 +60,15 @@ class ActionModel(val element: Element) {
}

private fun collectTypes(mirror: TypeMirror, depth: Int = 0): Set<ActionSuperType> {
//We want to add by depth
// We want to add by depth
val superTypes = typeUtils.directSupertypes(mirror).toSet()
.map { collectTypes(it, depth + 1) }
.flatten()
return setOf(ActionSuperType(mirror, depth)) + superTypes
}

class ActionSuperType(val mirror: TypeMirror, val depth: Int) {
val element = mirror.asElement()
val qualifiedName = element.qualifiedName()
val qualifiedName = mirror.asElement().qualifiedName()

override fun equals(other: Any?): Boolean {
if (this === other) return true
Expand All @@ -77,8 +78,6 @@ class ActionModel(val element: Element) {
return true
}

override fun hashCode(): Int {
return qualifiedName.hashCode()
}
override fun hashCode(): Int = qualifiedName.hashCode()
}
}
27 changes: 25 additions & 2 deletions mini-processor/src/main/java/mini/processor/MiniProcessor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,37 @@ import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.TypeSpec
import mini.Action
import mini.Reducer
import net.ltgt.gradle.incap.IncrementalAnnotationProcessor
import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType
import javax.annotation.processing.*
import javax.lang.model.SourceVersion
import javax.lang.model.element.TypeElement

/**
* Mini annotation processor.
*
* In order to support incremental annotation processing we must specify the kind of processing this
* does. For our use case, the processor looks up all the classes in the project in order to find
* [Reducer] and [Action] annotations and created a single [MiniGen] file with the info, so it would
* make our processor an AGGREGATING one.
*
* More info and implementations can be found in:
* Official docs:
* - https://docs.gradle.org/current/userguide/java_plugin.html#sec:incremental_annotation_processing
*
* Isolating incremental annotation processors implementations:
* - https://github.com/square/moshi/pull/824
* - https://github.com/JakeWharton/butterknife/pull/1546
*
* Aggregating incremental annotation processors implementations:
* - https://github.com/greenrobot/EventBus/pull/617
*
* Libraries used:
* - https://github.com/tbroyer/gradle-incap-helper
*/
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedOptions("kapt.kotlin.generated")
@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.AGGREGATING)
class MiniProcessor : AbstractProcessor() {

override fun init(environment: ProcessingEnvironment) {
Expand Down Expand Up @@ -46,6 +71,4 @@ class MiniProcessor : AbstractProcessor() {

return true
}


}
57 changes: 30 additions & 27 deletions mini-processor/src/main/java/mini/processor/ReducersGenerator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,47 +10,56 @@ import javax.lang.model.element.Element
import javax.lang.model.element.ExecutableElement

object ReducersGenerator {

fun generate(container: TypeSpec.Builder, elements: Set<Element>) {
val reducers = elements.map { ReducerModel(it) }
.groupBy { it.containerName }

val reducerContainerType = Any::class.asTypeName()
val reducerContainerListType = List::class.asTypeName().parameterizedBy(reducerContainerType)

val whenBlock = CodeBlock.builder()
val newDispatcherFn = FunSpec.builder("newDispatcher")
.returns(Dispatcher::class)
.addCode(CodeBlock.builder()
.addStatement("return Dispatcher(actionTypes)")
.build())
.build()

val subscribeSingleWhenBlock = CodeBlock.builder()
.addStatement("val c = %T()", CompositeCloseable::class)
.addStatement("when (container) {").indent()
.beginControlFlow("when (container)")
.apply {
reducers.forEach { (containerName, reducerFunctions) ->
addStatement("is %T -> {", containerName).indent()
beginControlFlow("is %T ->", containerName)
reducerFunctions.forEach { function ->
addStatement("c.add(dispatcher.subscribe<%T>(priority=%L) { container.%N(it) })",
function.function.parameters[0].asType(), //Action type
function.priority, //Priority
function.function.simpleName //Function name
)
}
unindent().addStatement("}")
endControlFlow()
}
}
.addStatement("else -> throw IllegalArgumentException(\"Container \$container has no reducers\")")
.unindent()
.addStatement("}") //Close when
.endControlFlow()
.addStatement("return c")
.build()

val registerOneFn = FunSpec.builder("subscribe")
val subscribeSingleFn = FunSpec.builder("subscribe")
.addModifiers(KModifier.PRIVATE)
.addParameter("dispatcher", Dispatcher::class)
.addParameter("container", reducerContainerType)
.addParameters(listOf(
ParameterSpec.builder("dispatcher", Dispatcher::class).build(),
ParameterSpec.builder("container", reducerContainerType).build()
))
.returns(Closeable::class)
.addCode(whenBlock)
.addCode(subscribeSingleWhenBlock)
.build()

val registerListFn = FunSpec.builder("subscribe")
.addParameter("dispatcher", Dispatcher::class)
.addParameter("containers", reducerContainerListType)
val subscribeListFn = FunSpec.builder("subscribe")
.addParameters(listOf(
ParameterSpec.builder("dispatcher", Dispatcher::class).build(),
ParameterSpec.builder("containers", reducerContainerListType).build()
))
.returns(Closeable::class)
.addStatement("val c = %T()", CompositeCloseable::class)
.beginControlFlow("containers.forEach { container ->")
Expand All @@ -59,23 +68,17 @@ object ReducersGenerator {
.addStatement("return c")
.build()

val initDispatcherFn = FunSpec.builder("newDispatcher")
.returns(Dispatcher::class)
.addCode(CodeBlock.builder()
.addStatement("return Dispatcher(actionTypes)")
.build())
.build()

container.addFunction(initDispatcherFn)
container.addFunction(registerOneFn)
container.addFunction(registerListFn)

with(container) {
addFunction(newDispatcherFn)
addFunction(subscribeSingleFn)
addFunction(subscribeListFn)
}
}
}

class ReducerModel(val element: Element) {
private class ReducerModel(element: Element) {
val priority = element.getAnnotation(Reducer::class.java).priority
val function = element as ExecutableElement
val container = element.enclosingElement.asType()
val container = element.enclosingElement.asType()!!
val containerName = container.asTypeName()
}