This repository has been archived by the owner on Dec 1, 2023. It is now read-only.
Update dependency dev.fritz2:core to v1.0-RC12 #17
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
1.0-RC1
->1.0-RC12
Release Notes
jwstegemann/fritz2 (dev.fritz2:core)
v1.0-RC12
: Version 1.0-RC12Improvements
ComponentValidationMessage
a data classFixed Bugs
PR #806 - Proper management of Store Lifecycle (Jobs, Handlers and Flows)
Motivation
Currently there is no real lifecycle-management for
Jobs
ofHandler
s andStores
. The initial idea was to hide those details from the user in order to make the API and overall user-experience as easy and pleasant as possible.Sadly this was a bad idea - not only does it lead to memory leaks, but also to unexpected behaviour due "handledBy" invocations running endlessly. Imagine a delayed change of a store's data initiated by a handler which is no longer "visible", because the
Mountpoint
in which the handler was called was already destroyed. This is definitely a problem, as it contradicts the reliable reactive behaviour of fritz2.That being said, especially the latter problem arises very seldomly, which is the reason why we encountered and subsequently adressed this problem so late on the final route to 1.0 version of fritz2.
Nevertheless we have to fix it, so here we go.
Conclusion
There is a need for an explicit lifecycle-management for all fritz2 elements which are related to reactive behaviour such as:
Store
sHandler
sHistory
- more or less for streamlining reasons. Appears rarely outside of aStore
oderWithJob
, so there are no behaviour changes)Router
- just for streamlining reasons. Appears only once inside an application, so there are no behaviour changes)Within a
RenderContext
, those elements should rely on the managedJob
of their context to be finished, as theRenderContext
gets dropped by anyrender*
-function. This solves the memory leaks as well as the unexpected behaviour of a neverending local store orhandledBy
call.Outside of any
RenderContext
or other receivers that offer someJob
, the job-handling should be made explicit. Typically this applies for global stores that hold application states. Those elements are discrete and rather small in number and are intended to run for the whole lifecycle of an application, so there is no harm in creating new jobs for those cases.Solution
RootStores
Previously, RootStores created their own jobs which were never cancelled. With this PR, every store initialization needs a
job: Job
parameter.Because the jobs within a
RenderContext
are already managed, we also added convenience functions which directly use theRenderContext
-job if a store is created with thestoreOf
-factory:Each store which is created outside of a
WithJob
-Scope or directly using the constructor needs a user managedJob
. The user himself is responsible to take care of the job lifecycle and cancelling the job when it is not needed anymore. We suggest using the job of the surrounding RenderContext, because that one is always properly managed.In real world applications, it is normal to have some globally defined
Store
s. Don't be afraid to simply create newJob
s for those without any "management" by yourself. Those stores are intended to run for as long as the application runs. So there is no need to stop or cancel them.Forever running jobs inside such global stores were a normal occurance before this PR and will remain so after this PR.
No more global handledBy
Previously,
handledBy
could be called everywhere. In aWithJob
-context (e.g. aRenderContext
), it used that job, otherwise it created a new job which was endlessly running within the application-lifecycle, which is what might have caused some unexpected side effects.Now you can only run
handledBy
withinWithJob
-scope (aRenderContext
implementsWithJob
)RootStore
-InstanceThis ensures that the
Job
used inside ofhandledBy
is always properly managed, which includes both cases:Store
which is intended to run foreverThe 'handledBy'-functions within a
RootStore
areprotected
and can therefore only be used within theRootStore
itself or in any derived custom store-implementation. ARootStore
as receiver - e.g. using extension functions or apply/run - is not sufficient!If you explicitely want to use the store-job outside the
RootStore
, you have to create an extension function with theWithJob
receiver and call that function within theRootStore
wrapped with the newrunWithJob
-function.Example:
Alongside with this change, a
handledBy
-call will also be interrupted if:handledBy
was called) has been cancelledAlso, the store's
data
-Flow will be completed when the store-job has been cancelled to prevent further side effects of the store.Improve Rendering
When using reactive rendering, e.g. using
Flow<T>.render
orFlow<T>.renderEach
, the render-operation was previously never interrupted if a new flow-value was emitted. The render-operations where queued after each other. This behaviour has changed with this PR. When theFlow
emits a new value, the current render-task will be interrupted and it will directly re-render the content with the latest value. This will improve performance.In rare circumstances, this might also cause different behaviour. In our opinion this could only happen when the reactive rendering is abused for state handling outside of
Store
s, for example with mutating somevar foo
outside of the rendering block. Since we consider such implementations bad practise, we recommend to change these constructs.Migration Guide
Stores
For all global
Store
s of an application, just add some newly createdJob
:If you encounter a compiler error due to missing
job
-parameter inside aRenderContext
, please have a look at the last section about "Chasing Memory Leaks" where the dangers and solutions as explained in depth.Global handledBy Calls
You simply have to move those calls inside some
Store
or someRenderContext
- there is no standard solution which fits all situations. You have to decide for yourself where the data handling fits best.If you have two stores and changes to one should also change the other, consider
handleAndEmit
as alternate approach or move the call to the dependent store (the one that holds the handler passed tohandledBy
).History
The
history
-function without receiver has been removed. So you either have to move the history code inside someStore
(which is the common and recommended approach) or you have to invoke the constructor manually:Router
If you really need to call the
Router
-constructors manually, you have to provide aJob
now:Consider using the
routerOf
-factories, they will create aJob
automatically.Chasing Memory Leaks
Besides the previously shown dedicated proposals for fixing compiler errors, we encourage you to scan your code for potential memory-issues we simply cannot prevent by our framework:
Imagine the creation of a
Store
inside aRenderContext
where a newJob()
is created:Last but not least: If you really want such a store to live forever, then refactor the code and define it as a global value.
There are variations of the pattern above which are even worse:
If you encounter any job-creation - thus manually created new
Job()
s - inside a looping-construct, this is very likely a mistake. Typical looping constructs appears insiderenderEach
or simplefor
-loops inside arender
. Inside such loops, you should also strive to re-use theJob
of the surroundingRenderContext
.For example, you should absolutely change code section like this one:
You should change such a spot by reusing the
RenderContext
'sjob
:Keep in mind that the first shown example may reside inside a loop, from where the
renderSomething
-function gets called: in that case, there is also a leak inside a loop, which is even harder to encounter - so always consider the job-creation inside aRenderContext
as code smell and bad practise.Credits
Special thanks to @serras and @realqdbp for their effords and contributions to this release!
v1.0-RC11
: Version 1.0-RC11Improvements
Fixed Bugs
beforeUnmount
Crashes due to Job CancellationsrenderEach
fast deleting Predecessorsv1.0-RC10
: Version 1.0-RC10Fixed Bugs
beforeUnmount
Callsv1.0-RC9
: Version 1.0-RC9Fixed Bugs
setFocus
from PopUpPanelv1.0-RC8
: Version 1.0-RC8Improvements
PR #783 / PR #788 - New utility render-functions for RenderContext
PR #783 and PR #788 add new utility
render
-functions insideRenderContext
. @serras initially brought up the idea and implementations to reduce boilerplate code by adding some convenience render-functions for common UI-patterns.Motivation
Often only some specific state of the data model should be rendered, while for other states the UI artefact should disappear. Typical cases are nullable types, where only for the none
null
part something should be rendered. The following new functions are added to the fritz2's core to adress those cases.Please regard: If you ever need to render some UI also for the other state-variants, then just refer to the standard
render
-function and use explicit case analysis likeif-else
orwhen
expressions inside the render-block.New Functions
The
renderIf(predicate: (V) -> Boolean)
function only renders the givencontent
, when the givenpredicate
returnstrue
for the value inside theFlow<V>
.The
renderNotNull()
function only renders the givencontent
, when the value inside theFlow<V?>
is not null.The
renderIs(klass: KClass<W>)
function only renders the givencontent
, when the value inside theFlow<V>
is a type ofklass
.PR #781: Migrate popper.js to Floating-UI and add Portalling
This PR adresses two closely related aspects within our headless components:
The first one is quite simple to explain: popper.js is outdated and therefor it makes sense to upgrade to the successor project. Floating-UI fits so much better than popper.js for fritz2 and our headless-components, as it is in fact by itself only a headless component. It offers functionality to calculate the position of some floating element, but it does no more render some styling by itself to the DOM. This obviously is great news for our implementations of popup-elements, as the integration removes some pain points and works much smoother together with fritz2's core mechanics!
For the user there are not much changes; the core approach to implement
PopUpPanel
as base for some floating brick, does not change. It is only about some properties that have different names and sometimes slightly different values. But we gain much more flexibility like the new concept of midlewares, that can be easily added by custom bricks. So in the end the user has more power to control and modify the floating element for his custom needs.The other aspect of this PR adresses fundamental problems with overlayed elements in general (this includes the headless modals and toast on top of the
PopupPanel
based components): There will always be a chance for clipping errors, if the overlay is rendered close to the trigger, which often is kinda deep down inside the DOM hierarchy. Please read about in the description of the Floating-UI project.That said, nothing will change on API level for this aspect. It is a pure implementation aspect, that has changed now: All headless-overlay components will now render their overlay-bricks inside the original target-node of the outer
render
-function, which acts as a portal-root. The trigger will create only some placeholder node into the DOM, whereas the real overlay content will be rendered inside the portal-root quite upside in the DOM. The placeholder acts as a portal to the final rendering place.This is a common pattern and so we stick to the wording inside our code and name everything about it with
portal
prefix.API-Breaking Remarks and Migration-Guide
Be aware that this PR might be API breaking for the
headless
package and the components, that implements thePopupPanel
-class for some of their bricks. Also your own Implementation that relies onPopupPanel
might break.We do not consider this API breaking for the whole project, so we prefer to present this under the improvement aspect. We have no knowledge about users, using our headless-component yet, so obviously this will affect primarely ourselves.
There is one action needed to keep the headless components working: To use portalling you have to render the
portalRoot
manually in our your render {} Block like this:After this patch, the Headless-Components
listBox
,menu
,modal
,popOver
,toast
andtooltip
should be working again.If you should against all expectations encounter a break, please simply refer to the updated list of properties within the headless documentation. This list and the following explanations should enable you to adapt to the new properties.
Further improvements
Fixed Bugs
Credits
Special thanks to @serras for his effords and contributions to this release!
v1.0-RC7
: Version 1.0-RC7Fixed Bugs
PopUpPanel
's arrow would initially be visible regardless of the popup being hidden.v1.0-RC6
: Version 1.0-RC6Breaking Changes
PR #772: Add Inspector.mapNull() method and fix Store.mapNull() path
This PR changes the behavior of
Store.mapNull()
so that the path of the derived Store is the same as in the parent Store. This is the correct behavior, as a Store created viamapNull()
does not technically map to another hierarchical level of the data model. As a result,mapNull()
works the same on both Stores and Inspectors, making the validation process more straight-forward. Previously, the Store's id has been appended to the path upon derivation.This might potentially be API breaking as the behavior of
Store.mapNull()
regarding the resulting path changes.Additionally, it adds an
Inspector.mapNull()
extension function that works likeStore.mapNull()
.Just like on a Store, a default value is passed to the method that is used by the resulting store when the parent's value is
null
.Improvements
PR #765: Adds convenience execution functions for Unit metadata in validators
For convenience reasons it is now possible to call a validator with metadata type
Unit
without the metadata parameter.Imagine a
Validator
of typeValidator<SomeDomain, Unit, SomeMessage>
:Fixed Bugs
v1.0-RC5
: Version 1.0-RC5Breaking Changes
PR #763: Validation: Remove explicit nullability from metdata type
This PR changes the
Validation
's metadata type to not be explicitly be nullable. Nullable types are still allowed, however.The
ValidatingStore
API has been changed accordingly.Validation
Before
Now
ValidatingStore
Before
Now
Additionally, the convenience factory
storeOf(...)
has been overloaded soValidatingStore<D, Unit, M>
s can be created without the need to specifyUnit
as the default metadata value manually.Migration
Validation<D, T, M>.invoke(...)
now always requires metadata to be present. Add the missing metadata if necessary.Validation<D, T, M>
constructor now requires the paramermetadataDefault
to be present. Add the missing metadats default if necessary.PR #761: Inspector based Validation
Motivation
Real world domain objects are in most cases forms a deep object hierarchy by composing dedicated types in a sensefull way, for example some
Person
consists of fields likename
orbirthday
but also complex fields like someAddress
, which itself represents some domain aspect with basic fields.As validation is most of the time tied to its corresponding domain type, the validators mirror the same hierarchy as the domain classes. Thus they must be composable in the same way, the domain types are composed.
This was prior to this PR not supported by the 1.0-RC releases!
Solution
The API of the
Validation
type changes just a little: Thevalidate
lambda expression now provides no longer a (domain) typeD
, but anInspector<D>
! There are now twoinvoke
functions that take as first parameter anInspector<D>
and as before just aD
, which then constructs the inspector itself. So one can use the latter for the (external) call of a validation with the domain object itself and the former for calling a validator from inside another validator!Remark: If you have used the recommended
validation
-factory functions, there will be no API breaking at all, as those already have used theInspector<D>
. So it is very unlikely that this change will break existing code!Example
The following composition now works:
Migration Guide
If you have used the
Validation.invoke
method directly, then prefer to switch to the dedicatedvalidation
-factories!Inside your validation code just remove the
inspectorOf(data)
line, that you hopefully will find and change the name of the parameter toinspector
.If you have not used any inspector based validation code, you simple must change the field access in such way:
Also try to replace handcrafted
path
parameters of theMessage
objects by relying on theinspector
object:Improvements
Fixed Bugs
Router
v1.0-RC4
: Version 1.0-RC4Improvements
Fixed Bugs
v1.0-RC3
: Version 1.0-RC3Breaking Changes
PR #728: Streamline API of Stores, Inspector and Lenses
We changed our API of
Store
,Inspector
andLens
to a more Kotlin-like style of functional programming, making them more similar to theFlow
-API.Migration Guide
The following tables show the difference:
Stores mapping
The same applies for the
Inspector
API as well.Lens creation
Lens mapping
PR #735: Optimize Efficiency of
render
andrenderText
Until now, the
Flow<V>.render
andFlow<V>.renderText
functions collected every new value on the upstream flows and started the re-rendering process.In order to improve performance however, a new rendering should only happen if there is not just a new value, but a changed one. If the value equals the old one, there is no reason to discard the DOM subtree of the mount-point.
This effect was previously achieved by adding
distinctUntilChanged
to the flow. But it is cumbersome in your code and easy to forget,so we added this call to the provided flow for both functions automatically.
As a result, the user gets automatic support for efficient precise rendering approaches by custom data-flows.
PR #731: Remove
FetchException
fromhttp
-APINo more
FetchException
is thrown whenexecute()
gets called internally for receiving theResponse
object in fritz2http
-API.Migration Guide
For this reason, it is not needed to catch the
FetchException
exception anymore to receive aResponse
with status-code !=200
. The only exceptions that can occur now are the ones from the underlying JavaScript Fetch-API (e.g. if status-code =404
).PR #739: Fixes focus-trap functions
This PR repairs the functionality of the
trapFocusWhenever
-function. Before, it behaved incorrectly, as setting the initial focus and restoring would not work properly. Now it is explicitly targeted to its conditionFlow<Boolean>
for its internal implementation.Also, it renames
trapFocus
totrapFocusInMountpoint
to improve its semantic context - enabling the trap inside a reactively rendered section and disabling it on removal.The so called "testdrive" was added to the
headless-demo
project, which offers (and explains) some samples in order to test and explore the different focus-traps. Also, some UI-Tests were added which stress those samples.Migration Guide
Just rename all occurences of
trapFocus
totrapFocusInMountpoint
:PR #740 Simplify Tracker
Until now, a tracker was able to distinguish several different transactions which were passed as a parameter to the
track
function. This rarely needed functionality can still be implemented by using multiple trackers (whosedata
streams can be combined if needed).Specifying a transaction as a parameter of
track
is no longer possible. Appropriately, nodefaultTransaction
can be defined in the factory either. Likewise, obtaining the flow which checks whether a certain transaction is running by invoking theTracker
is omitted. All of this significantly simplifies the tracker's implementation.Further Improvements
test-server
API.text
attribute extension functions.Fixed Bugs
v1.0-RC2
: Version 1.0-RC2Breaking Changes
PR #718: Remove Repositories from Core
As we have considered repositories to add no real value as abstraction, this commit will remove them entirely from fritz2.
Migration Guide
Just integrate the code form any repository implementation directly into the handler's code, that used to call the repository. Of course all Kotlin features to structure common code could be applied, like using private methods or alike.
PR #707: Repair remote auth middleware - prevent endless loop for 403 response
The default status code for a failed authentication is reduced to only 401.
Rational
Before also the 403 was part of the status codes and would trigger the handleResponse interception method and starts a new authentication recursively. This is of course a bad idea, as the authorization will not change by the authentication process. Therefore the default http status for launching an authentication process should be only 401.
Migration Guide
If you have some service that really relies on the 403 for the authentication, please adopt to the http semantics and change that to 401 instead.
PR #712: Simplify history feature
Simplifying the history feature, which includes the following changes:
Store
by defaultreset()
method toclear()
add(entry)
method topush(entry)
last()
method, cause withcurrent: List<T>
every entry is receivablecapacity
to0
(no restriction) instead of10
entriesPR #715: Exposing Store interface instead of internal RootStore and SubStore
Exposing only the public
Store<T>
type in fritz2's API, instead of the internal typesRootStore
orSubStore
for simplifying the use of derived stores.Migration Guide
Just change the type of some field or return type from
RootStore<T>
toStore<T>
andSubStore<T, D>
toStore<D>
.PR #727: Resolve bug with alsoExpression on Hook with Flow
In order to make the also-expression work with
Flow
based payloads, we had to tweak the API.The
Effect
now gets thealsoExpr
from theHook
injected into the applied function as second parameter besides the payload itself. This way the expression can and must be called from thevalue
assigning code sections, which a hook implementation typically implements.As the drawback we can no longer expose the return type
R
to the outside client world. An effect now returnsUnit
.migration guide
The client code of some hook initialization does not need any changes.
The code for hook execution should almost always stay the same, as long as the code did not rely on the return type. If that was the case, you have the following options:
The assignment code to
Hook.value
will need a second parameter. Often this is done by some functional expression, which can be solved like this (example taken fromTagHook
):New Features
PR #701: Add headless Toast component
A toast is a component that can be displayed in specific areas of the screen for both a fixed or indefinite amount of time, similar to notifications. fritz2's headless components now offer a nice and simple abstraction for this kind of functionality.
Have a look into our documentation to get more information.
PR #719: Enable Event capturing
It is now possible to listen on events in capture-phase (suffixed by
Captured
):For this we added new options to the
subscribe()
function which gives you aListener
for your event:Using the
init
-lambda you can make settings to the captured event that have to be applied immediately.We also fixed a bug when using
stopPropagation()
on aListener
which sometime did not work as expected.Further New Features
Improvements
PR #711: Improve handling of nullable values in Stores
Handling nullable values in
Store
sIf you have a
Store
with a nullable content, you can useorDefault
to derive a non-nullableStore
from it, that transparently translates anull
-value from its parentStore
to the given default-value and vice versa.In the following case, when you enter some text in the input and remove it again, you will have a value of
null
in yournameStore
:In real world, you will often come across nullable attributes of complex entities. Then you can often call
orDefault
directly on theSubStore
you create to use with your form elements:Calling
sub
on aStore
with nullable contentTo call
sub
on a nullableStore
only makes sense, when you have checked, that its value is not null:Further Improvements
Fixed Bugs
Configuration
📅 Schedule: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR has been generated by Mend Renovate. View repository job log here.