sergejsha / knot Goto Github PK
View Code? Open in Web Editor NEWUnidirectional reactive state container for Android & Kotlin.
License: Apache License 2.0
Unidirectional reactive state container for Android & Kotlin.
License: Apache License 2.0
In CompoisiteKnot
(should also be valid for Knot
) without any subscribers of the state, when a reducer throws an exception, the exception gets silently ignored and whole knot instance stops processing changes.
Solution: any exception thrown in the reduce function should lead to RxJavaPlugins.onError()
call and consequent crash.
Impacted version: 3.1.0 (rx2 and rx3)
Given:
change { intercept { } }
sectionWhen:
Actual:
knot.state
terminates with ClassCastException
because PrimeA tries to cast ChangeB to ChangeAExpected:
knot.state
doesn't terminatechange { intercept { } }
section declared for whole CompositeKnot
is capable of intercepting both change typesSomething with actions seems broken in 3.0.2
and I believe it could be this flatMap
:
fd78d39#diff-6eee9d229ac30d0f2976e4d1a66ffae4R285
My perform<Action>
observables keep stacking on top of each other for each new action emitted, causing infinite streams in switchMap
s to never be disposed.
Example:
perform<Action> {
switchMap {
Observable.never<Change>()
.doOnLifecycle({
// onSubscribe gets called for every new emitted action (as it should)
}, {
// onDispose never gets called (but it should get called for every switch)
})
}
}
We want to log each state
, change
and action
changes.
In the past we simply used something like this to print it to logcat:
knot<A,B,C> {
state {
watchAll { TAG.logDebug { "| state: $it" } }
initial = ...
}
changes {
watchAll {
TAG.logDebug { "+----------------" }
TAG.logDebug { "| change: $it" }
}
reduce { ... }
}
actions {
watchAll { TAG.logDebug { "| action: $it" } }
perform<...> { ... }
}
}
But we don't want to repeat these log statements in every class were we use knot.
So we thought about another implementation like a fun <A,B,C> loggableKnot<A,B,C>(block: ...): KnotBuilder<A,B,C>
But this is not going to work because each time we call the state
, change
or action
lambda a new instance of the StateBuilder
resp. ChangeBuilder
and ActionBuilder
will be created.
That means it is currently not possible to override (or better add) "something" to these builders "behind the scene".
The only solution we found was to have a extension function for each Builder
(State
, Change
, Action
):
Example:
fun <State : Any> StateBuilder<State>.logState(tag: String) {
watchAll { tag.logInfo { "| state: $it" } }
}
But this looks of course not so nice in the knot DSL:
knot<A,B,C> {
state {
logState(TAG)
initial = ...
}
changes {
logChanges(TAG)
reduce { ... }
}
actions {
logActions(TAG)
perform<...> { ... }
}
}
So what I want to have is something like this:
fun <State : Any, Change : Any, Action : Any> KnotBuilder<State, Change, Action>.logEverything(tag: String) {
state {
watchAll { tag.logInfo { "| state: $it" } }
}
changes {
watchAll {
tag.logInfo { "+----------------" }
tag.logInfo { "| change: $it" }
}
}
actions {
watchAll { tag.logDebug { "| action: $it" } }
}
}
But again, these will override each StateBuilder
, ChangesBuilder
and ActionsBuilder
.. But instead of overriding I want to "add" these things..
Or is there a another/better solution for something like this?
Currently the dependency to rxjava is an implementation
dependency.
That means it is not available for consumers of this library at compile time.
But you have open APIs to rxjava (like the knot.state
property).
That means if a project wants to use knot
and declare it as a dependency it will not work.
It will lead to compile time errors.
Long story short:
Rxjava should be declared as an api dependency so other project can directly start with knot without adding rxjava to their dependencies.
In the snippet below
actions {
perform<Action.Load> {
flatMapSingle<Payload> { api.load() }
.map<Change> { Change.Load.Success(it) }
.onErrorReturn { Change.Load.Failure(it) }
}
}
Action.Load
is not emitted once Change.Load.Failure(it)
is returned.
Given this test:
@Test
fun compositeKnotTest() {
data class X(val x: Int = 0)
val compositeKnot = compositeKnot<X> {
this.state {
initial = X()
}
}
compositeKnot.registerPrime<Unit, Action> {
changes {
reduce<Unit> {
return@reduce X().only
}
}
events {
source {
compositeKnot.state
.doOnNext { println(it) }
.map { Unit }
}
}
}
compositeKnot.compose()
compositeKnot.change.accept(Unit)
Thread.sleep(3000)
}
I would expect that after I call compositeKnot.change.accept(Unit)
, the reducer get called, which creates a new X()
and that's it.
Why don't I expect a call to doOnNext
?
Because X
is a data class
and 100% equal to the previous state.
But what happen currently?
We run into an endless loop.
After the reducer
creates a new X()
the compositeKnot.state
get called, return a new Change
which will call the reducer
which creates a new X()
which calls the compositeKnot.state
.... recursively...
As I said, I would expect (because the new X is equal to the new X) the compositeKnot.state
would not be triggered.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.