Annotation processor library that generates a stateful wrapper and an Update listener interface for model classes.
You have a simple Model and a View that renders the model.
data class Model(
val title: String,
val subtitle: String,
val items: List<Item>?
)
class View {
fun render(model: Model) {
/* renders the model */
}
}
Instead of rendering everything on every update or checking the updates manually
@Stateful
data class Model( ... )
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.
class StatefulModel {
fun accept(newModel: Model) { ... }
fun clear() { ... }
sealed class Property {
object TITLE: Property
object SUBTITLE: Property
object ITEMS: Property
}
}
Create functions that render the properties and decorate them with the @Renders
annotation,
passing the appropriate Property
implementation class.
class View {
private val statefulModel by stateful()
fun render(model: Model) {
statefulModel.accept(model)
}
@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 */ }
}
You can create your rendering functions using any (even more than one per property) of the following signature configurations:
- New value:
@Renders(StatefulModel.Property.TITLE::class)
fun renderTitle(newTitle: String) { /* update the title view */ }
- Current value (must be optional) and new value:
@Renders(StatefulModel.Property.TITLE::class)
fun renderTitle(currentTitle: String?, newTitle: String) { /* update the title view */ }
- New model:
@Renders(StatefulModel.Property.TITLE::class)
fun renderTitle(newModel: Model) { /* update the title view */ }
- Current model (must be optional) and new model:
@Renders(StatefulModel.Property.TITLE::class)
fun renderTitle(currentModel: Model?, newModel: Model) { /* update the title view */ }
The Stateful
annotation has a type
argument, with a default value of StatefulType.INSTANCE
.
- The
INSTANCE
type caches only the current value and performs diffing of new instances with the respective current. - The
STACK
type holds a stack cache and allows performing arollback
to previous instances. What that means is that upon performing a rollback, the previous instance before the current one is accessed, diffing is performed with the current and the previous instance and the previous 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 instance is not announced and will only be announced when reaching it while going forth.
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. 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. 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 aRendererConfigurationException
with aRendererConfigurationError.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.
- Generate an access token with
read packages
permission, more details here: GitHub Help - Add the maven repository to your
Project
dependencies;username
is your user ID andpassword
is the key generated previously
allprojects {
repositories {
maven {
url = uri("https://maven.pkg.github.com/iFanie/Stateful")
credentials {
username = ...
password = ...
}
}
}
}
dependencies {
implementation 'dev.fanie:stateful:0.4.0'
kapt 'dev.fanie:stateful-compiler:0.4.0'
}
Copyright 2020 Fanis Veizis
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.