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.
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Observable State for WorkflowSwiftUI #283
Observable State for WorkflowSwiftUI #283
Changes from all commits
512fd39
4c67859
1501911
e4dc19d
f77ee5a
3522b1d
d080efd
354eb49
File filter
Filter by extension
Conversations
Jump to
There are no files selected for viewing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Question for myself]
Confirm we want this and below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised to see that need this here... Can this be hoisted into a shared layer? It feels like it should be avoidable boilerplate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It cannot unfortunately. This is a requirement for the Observation backport to work on iOS <17. You can learn more in this Point-Free series on Observation or in the docs for Perception.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, maybe I'm not 100% understanding, but this more reads to me that this is about leveraging the backport, not that we need this in the view itself... Eg I'm imaging if we had a view that lived right above this that Market owned, that could do something like
Or similar?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might expect it to work that way, especially coming from Blueprint, but SwiftUI bodies are evaluated independently, not as a recursive operation. You can't directly call into
wrapped.body
like that. If you built a wrapper like this:You'd find that
PerceptionWrapper.body
doesn't evaluatecontent.body
at all — it's evaluated later by itself. So theWithPerceptionTracking
wrapper, which captures observations synchronously, won't capture any observations done insideContent
.This is actually a good thing, because it allows for
PerceptionWrapper
andContent
to be re-evaluated independently. But it means that any view using aStore
must add its ownWithPerceptionTracking
wrapper to track the observations in its own body.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it, makes sense!
I still don't think it's really expected / reasonable to expect everyone to have to wrap like this – perhaps a good case for a macro of our own?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you elaborate on that idea? Not sure how I see how that could be implemented. Open to suggestions and contributions if anyone has a better way!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's an unfortunate necessity, and we should expect that views that take escaping closures will force us to even write multiple
WithPerceptionTracking
wrappers in the same view body, likeAFAIK TCA hasn't come up with any sugar for this.
We do have a runtime warning that you'll see if you access an
ObservableState
type's property outside of aWithPerceptionTracking
, correct?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's right. You'll get a "purple" runtime warning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh, I thought that Apple had fixed this somehow, but I don't remember how...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking we could potentially add a type-level macro that could mutate the
body
and add theWithPerceptionTracking
call, but perhaps that not possible.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed that something like
store.count += 1
works equally as well here, instead of sending an action.Is there any guidance around how to choose between actions, bindings (e.g. for a TextField), or direct mutation? Asked another way, how tightly do we want to embrace/enforce the unidirectional data flow aspect of Workflow?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the author of
CounterWorkflow
have the option to declareCounterWorkflow.State.count
asfileprivate(set) var
, allowing it to be mutated only byCounterWorkflow.Action
, and making it impossible to mutate it directly from a view or create a binding from it?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, when I did that locally it prevented both direct mutation and the creation of bindings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The unidirectional flow is enforced either way. Mutations from setters like
store.count += 1
are routed through aStateMutationSink
, which sends a genericAnyWorkflowAction<WorkflowType>
under the hood.The choice of when to use a custom action is probably going to be driven by:
In other words, "whenever you need it or want it", heh.