I would like to ask a question about the best way ...
# compose-desktop
a
I would like to ask a question about the best way to mutate your data with Compose. I am writing my font editor with Compose desktop. It has a hierarchical object model like in the image. So I have a bunch of mutable data classes that serialize to JSON. Additionally, I want to have features like being able to undo/redo. So far how I've implemented it is, mutable classes but with special setters and observable collections that keep track of changes and store them in the parent "glyph" for glyph-level undo. Is this how it should be implemented in Compose? Or is it more native to use like immutable data structures or some other way? Appreciate y'all opinions.
c
Just in general, Compose doesn’t play the nicest with mutable classes, and undo/redo will be difficult to implement if you’re modifying those properties directly. It’s best to use all immutable models to make sure any changes get updated in the Compose UI correctly. There are basically 2 ways to implement undo/redo functionality: State-based, and command-based. This article does a decent job of explaining the these two concepts and showing code snippets in JavaScript. I actually just released an experimental feature for my Ballast MVI library today which adds state-based undo-redo functionality automatically in version 2.2.0. The MVI pattern gets you writing your UIs in a manner where you already have both your “commands” and “states” explicitly described and managed for you by the library, and this new feature plugs into that process to track the changes necessary for undo/redo. You can view the documentation here, play with an example, join us over at #ballast for help getting started using Ballast or learning how to use this new feature.
d
If you have a mutable tree of data you need to have a way to communicate to the UI that the underlying data has mutated and to recompose. That's not a big deal accept for changes to the data tree happening in parallel and in a non atomic fashion. Mainly I am concerned about changes to lists while they are being read. You will get an exception for that.
The order of operations is this: 1. Perform an operation on the tree. 2. Emit a copy of the tree as an immutable object. 3. Repeat.
a
ya I just implemented a tree composable and basically did that ^^^, I've got a mutable tree data structure, but it emits an immutable copy of the tree for compose to consume
a
Thanks guys. @dewildte @Adam Brown In my case the tree can be quite large... a font can have up to 65K glyphs, and then each glyph has path and points - does the immutable copy copy everything on every change?
a
yup, sure does
a
Actually, I guess if I do Glyph level undo I could only do a copy of the glyph, and use kotlin immutable collections to reuse parts of the tree (https://github.com/Kotlin/kotlinx.collections.immutable)
a
on the compose side of things, the rendering is done using a lazy list, so thats fine, but the data is fully copied
a
@Adam Brown, gotcha, does it mean I need 2 types of the data? Like, Glyph and ImmutableGlyph and compose displays the immutable glyph?
a
thats what i have
well i have TreeNode, and ImmutableTreeNode
the actual data that the nodes point to is different
d
When would ever have 65K items visible?
a
no
my max is probably in the hundreds
d
Or existing in memory all at once?
a
yes exist in mem together though
d
Why in memory?
Can you not use a database?
a
the tree nodes are probably simple/small enough to have all 65k loaded at once
in my case the data they point to is a bit larger
no database in my use case, it's representing file system items
a
@dewildte They would not be visible all at once, but they're displayed in a scrollable list
d
65K+ mutable lists lol
c
An interesting ksp library, kopykat could make it easier to maintain your models. Just write them all as immutable, and you’d only need to consider them as mutable when making the changes to them. 65k glyphs is probably a bit much to hold in memory all at once. Even if not technically too large to fit in memory, it might just be a bit cumbersome to work with all that data at once memory, consuming more RAM and making the processing slower than it needs to be. What you might do instead, is when opening the font, first parse the file and cache it all in a local database of some kind. You than then query/page that database for displaying in the UI, and you could update glyphs one-at-a-time just by modifying the db record for it. And then in the background, you have it periodically writing that local DB cache back to the font file
a
whoa, thats a cool library, thanks for sharing!
d
I love how they spoke about the Optics library at the bottom.