-
Notifications
You must be signed in to change notification settings - Fork 76
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
Prototype different state designs #1077
Conversation
Added a demo of an interface approach in CatchUp here to offer a more side-by-side example: ZacSweers/CatchUp#1168 |
Personally, I'm not a huge fan of any of these 😬. They all feel a bit like papering over the cracks of us merging The vague idea I've had in the back of my head is making the event sink an explicit part of the
and then pass the same flow in in a similar fashion to the |
Can you share some more context about the motivations for these ideas? These ideas don't feel very different from the current design. I don't fully understand the differences between ideas I'd advice against
Sure you can achieve this by having a semantic In general I agree with @chrisbanes that keeping the event sink separate is always a good idea, if that ship hasn't sailed yet. |
Circuit actually did this initially (matching what Broadway does iirc) before we moved to this continuable state pattern, though the sink came in as a param to
If there's a way to support this again for those cases where it's easier/simple enough I'd be game, but I wasn't able to figure out a way that doesn't just end up with two different presenter interfaces. Your constructor approach is interesting though, and maybe there's a path there that involves supporting the event sink as an assisted injection parameter. That complicates some other API surface areas, but open to suggestions.
Yep! They've even got a test for this case: https://github.com/drewhamilton/Poko/blob/main/poko-tests/src/commonTest/kotlin/SimpleWithExtraParamTest.kt |
re: motivations Mostly just explorations for fun. Adam's a big advocate (and correct me if I'm wrong!) of the snapshot-driven state as a means to make recompositions much more granular without having to really think about it or worry about some of the stuff we have now with new state emissions incurring them. The event sink as a property participating in equals/hashcode has also been a long annoyance as it makes tests feel a little impure when you can't actually compare two states. |
Here's maybe a fun exercise: when you see just Are you assuming that I am also a fan of the snapshot-state object flexibility (probably influenced by Adam), and I really like the 3 version of the
When I'm creating But since I've been using that pattern a lot, I may have a different view when initially reading |
The list item example is being used above, but the reality is that you wouldn't pass down the Your top level UI composable would receive the State, and you'd the deconstruct it and pass down the appropriate sub-state/lambdas. Unless anyone has a real world example of this? |
Yeah I agree with Chris, I would conventionally write that function like this @Composable
fun FooListItem(model: M, onLeftSwipe: () -> Unit) {
Box(
Modifier.onLeftSwipe(onLeftSwipe)
)
} |
Gonna close this out, may probably write these out to a section of the doc site for posterity and call it a day. Thanks for the inputs! |
Based on some conversations with @adamp and @jingibus
There are a few different examples here.
AsDataClass
- a canonical Circuit state data class with an encapsulated event sink.TestEventSink
AsPokoClass
- similar toAsDataClass
, but would use the Poko library to generateequals
/hashCode
/toString
for every property but theeventSink
. Events in this case would move to aEventSink
interface, where events are sent instead to asend()
function.TestEventSink
AsInterface
- an interface implementation of state, more akin to traditional Compose states with interfaces backed byState
objects and driven by snapshots. This exposes no event sink, but rather semantically meaningful functions. These state objects areremember
'd and only one is ever emitted.State
objects. More granular recomposition in the UI as a result.var
,MutableState
, missingremember
, etc calls.TestEventSink
. Developers may reach for mocking + verify if they aren't using a functional presentersnapshotFlow
could still be useful in tandem with Turbine, but most of the existing test infra in Circuit isn't useful for this pattern.This boils down to two patterns - immutable state trees (1 and 2) and stable snapshot-driven objects (3). 1 and 2 are more traditional UDF, 3 is more traditional compose.
Others presented as examples
4.
AsInterfaceWithEventSink
- similar toAsInterface
, but with anEventSink
supertype for event objects rather than function calls.5.
AsAbstractClassWithEventSink
- something in between the 1 and 3, with the best/worst of both worlds.One really great thing here is that Circuit's current published API supports all of these cases, so its preference/guidance for one doesn't inherently prohibit the use of the others.