Skip to content

Commit

Permalink
Update for 0.4.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Flavio Veizi committed Jun 17, 2020
1 parent 410fc85 commit c5d8231
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 37 deletions.
106 changes: 72 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ You have a simple Model and a View that renders the model.
```kotlin
data class Model(
val title: String,
val titleFontSize: Float?,
val isTitleVisible: Boolean
val subtitle: String,
val items: List<Item>?
)

class View {
Expand All @@ -30,50 +30,74 @@ data class Model( ... )

#### Build.

For any given annotated type, the processor will generate two classes
- A listener interface with functions that will br invoked when each individual public property
is updated. For each property there will be 4 granular overloads of the callback, each with a
default implementation to avoid clutter.

```kotlin
interface StatefulModelListener {
fun onTitleUpdated(newTitle: String) {}
fun onTitleUpdated(oldTitle: String?, newTitle: String) {}
fun onTitleUpdated(newModel: Model) {}
fun onTitleUpdated(oldModel: Model?, newModel: Model) {}

fun onTitleFontSizeUpdated(newTitleFontSize: Float?) {}
...
}
```

- A wrapper class through which the new model instances will be passed and which, after diffing
through the public properties, will invoke the listener appropriately.
The processor will generate a wrapper class for your Model, through which the new model instances
will be passed and which, after diffing through the public properties, will invoke the listener
appropriately.

```kotlin
class StatefulModel {
fun accept(newModel: Model) { ... }
fun clear() { ... }

sealed class Property {
object TITLE: Property
object SUBTITLE: Property
object ITEMS: Property
}
}
```

#### Implement the generated interface.
Override whatever makes sense and pass all model updates through the Stateful wrapper class.
#### Create your property renderers.
Create functions that render the properties and decorate them with the `@Renders` annotation,
passing the appropriate `Property` implementation class.

```kotlin
class View : StatefulModelUpdateListener {
class View {
private val statefulModel by stateful()

fun render(model: Model) {
statefulModel.accept(model)
}

override fun onTitleUpdated(newTitle: String) {
/* update the title */
}
@Renders(StatefulModel.Property.TITLE::class)
fun renderTitle(newTitle: String) { /* update the title view */ }

@Renders(StatefulModel.Property.SUBTITLE::class)
fun renderSubtitle(newSubtitle: String) { /* update the subtitle view */ }

@Renders(StatefulModel.Property.ITEMS::class)
fun renderItems(items: List<Item>?) { /* update the items view */ }
}
```

##### Renderer arguments.
You can create your rendering functions using any (even more than one per property) of the following
signature configurations:

* New value:
```kotlin
@Renders(StatefulModel.Property.TITLE::class)
fun renderTitle(newTitle: String) { /* update the title view */ }
```

* Current value (must be optional) and new value:
```kotlin
@Renders(StatefulModel.Property.TITLE::class)
fun renderTitle(currentTitle: String?, newTitle: String) { /* update the title view */ }
```

* New model:
```kotlin
@Renders(StatefulModel.Property.TITLE::class)
fun renderTitle(newModel: Model) { /* update the title view */ }
```

* Current model (must be optional) and new model:
```kotlin
@Renders(StatefulModel.Property.TITLE::class)
fun renderTitle(currentModel: Model?, newModel: Model) { /* update the title view */ }
```

### Stateful types
The `Stateful` annotation has a `type` argument, with a default value of `StatefulType.INSTANCE`.

Expand All @@ -85,17 +109,31 @@ The `Stateful` annotation has a `type` argument, with a default value of `Statef
instance becomes the current.
- The `LINKED_LIST` type holds a linked list cache and allows rolling both back and forth. If a
new instance is accepted while being in any state other than the latest, then the newly accepted
istance is not announced and will only be announced when reaching it while going forth.
instance is not announced and will only be announced when reaching it while going forth.

### Stateful options
The `Stateful` annotation has an `options` array argument, with an empty default value.

- When the `NO_LAZY_INIT` option is applied, the top level functions for lazy delegation of the
generated wrapper initialization will not be generated.
- When the `NON_CASCADING_LISTENER` option is applied, only a single listener interface will be
generated containing callbacks for every single public property in the annotated model.
generated wrapper initialization will not be generated. Use this if you intend to use the
constructors directly and don't need the overhead.
- When the `NO_DIFFING` option is applied, no diffing will be performed on the properties of the
annotated model and the listener will be invoked on every new instance received.
annotated model and the listener will be invoked on every new instance received. Use this if you
do not need any diffing but would like the rest of the provided setup.
- When the `ALLOW_MISSING_RENDERERS` option is applied, the listener is allowed to omit a subset of
properties when creating `@Renders` annotated functions which will cause a
`RendererConfigurationException` with a `RendererConfigurationError.NO_MATCHING_RENDERERS_FOUND`
error to be to be thrown otherwise. Use this if you don't care to render all the properties of
the Model in the current View.
- When the `WITH_LISTENER` option is applied, separate interfaces, one for each public property of
the annotated model, will be generated, all of which are extended by a master interface. Implementing
the master interface allows you to have the functionality without needing to annotate anything.
Use this if you don't want to have extra annotations in your View or don't like/want reflection in
your code.
- When the `WITH_NON_CASCADING_LISTENER` option is applied, only a single listener interface will be
generated containing callbacks for every single public property in the annotated model. Use this
if you want to have the Listener setup don't want multiple interfaces added to the class count.


### Installation
#### Configure your project to consume GitHub packages.
Expand All @@ -121,8 +159,8 @@ allprojects {

```groovy
dependencies {
implementation 'dev.fanie:stateful:0.3.1'
kapt 'dev.fanie:stateful-compiler:0.3.1'
implementation 'dev.fanie:stateful:0.4.0'
kapt 'dev.fanie:stateful-compiler:0.4.0'
}
```

Expand Down
20 changes: 20 additions & 0 deletions app/src/main/java/dev/fanie/statefulapp/ModelRenderer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,24 @@ class ModelRenderer : AppCompatActivity(), View<Model> {
fun renderTitle(title: String) {
/* Renders the new title */
}

@Renders(StatefulModel.Property.TITLE_COLOR::class)
fun renderTitleColor(color: Int) {
/* Renders the new title color */
}

@Renders(StatefulModel.Property.TITLE_FONT_SIZE::class)
fun renderTitleSize(fontSize: Float?) {
/* Renders the new title font size */
}

@Renders(StatefulModel.Property.IS_TITLE_VISIBLE::class)
fun renderTitleVisibility(isVisible: Boolean) {
/* Renders the new title visibility */
}

@Renders(StatefulModel.Property.TITLE_ANIMATES::class)
fun renderTitleAnimation(animates: Boolean?) {
/* Renders the new title animation */
}
}
4 changes: 2 additions & 2 deletions buildSrc/src/main/kotlin/Project.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
object Project {
const val BUILD = 19
const val BUILD = 20
const val GROUP_ID = "dev.fanie"
const val VERSION = "0.3.1"
const val VERSION = "0.4.0"
}
2 changes: 1 addition & 1 deletion stateful/src/main/kotlin/dev/fanie/stateful/Renders.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ annotation class Renders(val property: KClass<out StatefulProperty<*, *>>)
*/
enum class RendererConfigurationError(val messageFormat: String) {
NO_RENDERERS_FOUND("NO_RENDERERS_FOUND: No functions annotated with @Renders found in `%s`."),
NO_MATCHING_RENDERERS_FOUND("NO_MATCHING_RENDERERS_FOUND: No functions annotated with @Renders for `%s` found in `%s`."),
NO_MATCHING_RENDERERS_FOUND("NO_MATCHING_RENDERERS_FOUND: No functions annotated with @Renders for `%s` found in `%s`. If you do not need to render every property, consider using the [StatefulOptions.ALLOW_MISSING_RENDERERS] option."),
INVALID_RENDERER_PARAMETERS("INVALID_RENDERER_PARAMETERS: Function `%s` should contain one of the following combinations of parameters: (newValue), (oldValue?, newValue), (newModel), (oldModel?, newModel)."),
INVALID_RENDERER_PARAMETER_TYPE("INVALID_RENDERER_PARAMETER_TYPE: Parameter `%s` of function `%s` should either be of `%s` or `%s` type."),
WRONG_RENDERER_PARAMETER_NULLABILITY("WRONG_RENDERER_PARAMETER_NULLABILITY: Parameter `%s` of function `%s` should be (optional = %s)."),
Expand Down

0 comments on commit c5d8231

Please sign in to comment.