staltz / ama

Ask me anything!
https://blog.sindresorhus.com/answering-anything-678ce5623798
93 stars 1 forks source link

Nested Dialogues - "not necessarily acyclic" #30

Closed DavidMihola closed 7 years ago

DavidMihola commented 7 years ago

Hi,

once again I have come here to ask about your thought process or design guidelines when writing cycle/MVI/nested dialogue programs. I have asked the same question a few months ago on Hannes Dorfmann's blog post about MVI so I am going to mostly repeat it here:

I'm currently doing a lot of stuff for AndroidTV and FireTV - and I am having trouble integrating the remote input on these devices with a MVI/cycle.js-ish architecture. I think it's fair to say that a screen interface always has some "implicit state" that controls which signals/intents it can send at any given moment: some Buttons may be disabled, EditTexts may be hidden, etc. On the remote, of course, any button can be pressed at any moment, but its meaning may vary with the current (UI-)state: Play/Pause may normally toggle video playback, but when seeking it may be used to confirm the new position and restart the stream at that position.

Therefore I am tempted to either:

I think I like the second approach best, but would be interested to hear your thoughts - do you also have "multi-pass" mappings in some of your main/model functions?

Thanks for any comments!

staltz commented 7 years ago

Hi!

I think it's fair to say that a screen interface always has some "implicit state" that controls which signals/intents it can send at any given moment

Yeah

the crucial point here is that I need to run every button press by the state to infer the intent and therefore how it should then update the state. Is this what you mean in your "Unidirectional User Interface Architectures" article when you say that it is "not necessarily an acyclic graph"? It seems I would need a cycle from the input to (some part of) the state to infer the intent and then back into the state to update it.

Yes, and this is what I would recommend. It's okay if your app is state-driven, and it's also okay to have a loop between that state and intent/view. In fact this is what we do in https://github.com/staltz/cycle-onionify, once you call onionify() it will create a cycle of state internal to your app, besides the outer cycle of main with drivers. (Picture it as a small circle tangentially to a large circle. The small one is inside the large one)

Onionify uses reducers which you can understand as transactions. They take the previous state and some incoming data from intent, and decide whether or not to apply a change and return a new state. It's somewhat similar to redux, but not so much, check the readme for the differences. I like how in Onionify everything is layered like an onion ("fractal style"), initial state is determined with a reducer, and lenses are super useful (sort of fulfills the purpose of a "ViewModel" like in MVVM, allowing you to prepare state in a suitable format for the view, or for children components).

For a real world example of onionify being used, check out https://github.com/staltz/matrixmultiplication.xyz.

Hope this helps.

DavidMihola commented 7 years ago

As always, thank you very much for your quick and helpful reply!

Yes, I had a look at the matrix multiplication example shortly after you published it, but I have to admit that some parts of it went over my head... But maybe with your remarks about onionify I can now make more sense of it!