nfrankel
01/04/2021, 2:17 AMMutableState
?
(i’ve close to zero experience in android)kenkyee
01/04/2021, 2:39 AMnfrankel
01/04/2021, 2:40 AMkenkyee
01/04/2021, 2:41 AMnfrankel
01/04/2021, 2:42 AMviewmodel
seems to be an android thing 😅kenkyee
01/04/2021, 2:42 AMnfrankel
01/04/2021, 2:46 AMdelegate by observable
i just want binding between two variables
but because compose seems to frown on classes, i’m stuck
🤔Adam Powell
01/04/2021, 2:46 AMderivedStateOf
might be what you're looking for if you want snapshot state that depends on other snapshot statenfrankel
01/04/2021, 2:48 AMderivedStateOf
the class thing is what i’m deducing after a couple of hours
perhaps i’m completely mistakenAdam Powell
01/04/2021, 2:50 AMclass MyState(
initialFoo: Foo
) {
var foo by mutableStateOf(initialFoo)
val bar by derivedStateOf { foo.computeBar() }
}
nfrankel
01/04/2021, 2:52 AMAdam Powell
01/04/2021, 2:54 AM@Composable
functions to act as transformations of app state to UI. The input state is generally hoisted out of the @Composable
functions themselves as code scalessnapshotFlow {}
nfrankel
01/04/2021, 2:58 AMAdam Powell
01/04/2021, 2:59 AMnfrankel
01/04/2021, 3:03 AM@Composable
i think it’s the main building blockAdam Powell
01/04/2021, 3:05 AMnfrankel
01/04/2021, 3:06 AMAdam Powell
01/04/2021, 3:07 AMnfrankel
01/04/2021, 3:07 AMLet’s look at this more practically in the context of Android development today and take the example of a view model and an XML layout.😂
Adam Powell
01/04/2021, 3:07 AMnfrankel
01/04/2021, 3:20 AM@Composable
function if any of its @Composable
parameters has been changed
correct?Colton Idle
01/04/2021, 7:11 AMkenkyee
01/04/2021, 11:25 AMnfrankel
01/04/2021, 11:47 AMYesThanks 🙂
It very much is like React and Flutterwell, those references are lost on me as i know none of them 😅
kenkyee
01/04/2021, 12:04 PMColton Idle
01/04/2021, 1:51 PMnfrankel
01/04/2021, 1:52 PMAdam Powell
01/04/2021, 6:44 PMnfrankel
01/04/2021, 6:47 PMAdam Powell
01/04/2021, 6:53 PM@Composable
functions that return Unit
being PascalCased
as types/entities, but that one's minor and the lint hinting for it will probably make its way to an idea plugin/config eventually 🙂by mutableStateOf
-backed classesMutableState<T>
parameters are usually a canary that signals that you want to establish a more specific API with the UI elementapplyButton
is a good example: this rename
inner function used as the onClick
handler would be a great fit as a method on such a class, or an extension function with such a class as its receiver: https://github.com/ajavageek/renamer-compose/blob/master/src/main/kotlin/ApplyButton.kt#L17suspendCancellableCoroutine
to bridge suspend
functions and callback-driven async code*Effect
composables: SideEffect
, DisposableEffect
and LaunchedEffect
, depending on what you're up tochanged
state intending to model?SwingPanel
bits look suspicious but I think that might be on our end rather than yours 🙂nfrankel
01/04/2021, 8:08 PMchanged
is interestingAdam Powell
01/04/2021, 10:10 PMval candidates = remember {
derivedStateOf {
val regex = pattern.value.toRegex()
val replace = replacement.value
files.map {
if (pattern.value.isBlank()) it
else {
val newName = regex.replace(it.name, replace)
File(newName, it.parent)
}
}
}
}
State<List<File>>
instance that will automatically update whenever the things it reads changeSwingPanel
API being used to present the file table, and I'm having trouble locating the source code for it to verifyRow
recomposes, new swing components are created instead of updating existing ones, which seems suspectnfrankel
01/05/2021, 10:37 AMderivedStateOf
thanks for letting me know 👍though I think there might be something structurally problematic with the😬API being used to present the file table, and I’m having trouble locating the source code for it to verifySwingPanel
it looks like you would need to observe snapshot changes of your list and notify your AbstractTableModel listeners of the changesso back to the old manual observer pattern again?
derivedStateOf
works perfectly!
now i just need a way to “refresh” the state when i’ve renamed the filesAdam Powell
01/05/2021, 3:37 PMsnapshotFlow { theTableState }
collect call to send AbstractTableModel
data change notifications I think you should be in businessnfrankel
01/06/2021, 8:44 AM[path]
=(derived state)=> files
• [path, pattern, replacement]
=(derived state)=> candidates
• [files, candidates]
=(@Composable)=> filemodel
the button renames files
so the only origin i’ve is the filesystem itself
which is outside the scope of compose 😅
with a legacy observer pattern, i just send an event from the button that is subscribed to by the model and that calls refreshAdam Powell
01/06/2021, 4:28 PMderivedStateOf
state object will always be up to date for instantaneous reads, like you would do from a button click handlernfrankel
01/06/2021, 5:04 PMyou should be able to do the same here if you want refresh to be manualdo you have a link to the API to explicitly call a refresh?
I presumed that you wanted the table to refresh automatically as the files changethat’s the case, i just don’t understand how
Adam Powell
01/06/2021, 7:48 PMAbstractTableModel
is outside compose, it looks like you'll want to update it outside of composition, but with a subscription scope driven by the composition of your inserted swing UI. Consider something like:Row(padding.fillMaxWidth().fillMaxHeight(0.85f)) {
// fileModel now accepts the wrapped types, not State<T>.
// We use the LaunchedEffect below to scope a subscription that pushes updates to it.
val model = remember { fileModel(files.value, candidates.value) }
// Monitor candidates and notify the model of updates
LaunchedEffect(model) {
// snapshotFlow runs the block and emits its result whenever
// any snapshot state read by the block was changed.
snapshotFlow { Pair(files.value, candidates.value) }
.collect { (currentFiles, currentCandidates) ->
// assume this call internally invokes fireDataTableChanged()
model.updateFilesAndCandidates(currentFiles, currentCandidates)
}
}
// Don't recreate the swing UI elements on every recomposition
val pane = remember(model) { JScrollPane(FileTable(model)) }
SwingPanel(pane)
}
nfrankel
01/06/2021, 8:24 PMAdam Powell
01/07/2021, 3:58 PMnfrankel
01/07/2021, 5:57 PMfiles
to be refreshed
candidates
is derived data, so it will be updated automaticallyfiles
is getting the children of the path
which doesn’t change by itselfAdam Powell
01/07/2021, 6:23 PM@Composable fun MyComposable() {
val recomposeMe = invalidate // returns an invalidator for this current scope
Button(onClick = { recomposeMe() }) { /* ... */ }
}
is how you use it.by mutableStateOf
- this class would be your data model that holds files
and the other derived properties, and you would remember
an instance of it rather than several disjoint state objects. Then you can define a refresh()
function on that class, which performs the refresh, sets files.value
and then any necessary recomposition happens as a result of that data change.nfrankel
01/07/2021, 6:27 PMkenkyee
01/07/2021, 11:22 PMnfrankel
01/08/2021, 5:02 AMAdam Powell
01/08/2021, 10:00 PMandroidx.compose.runtime.getValue
and androidx.compose.runtime.setValue
but you can import them manually. Then you can write
var path by mutableStateOf(initialPath)
and skip all of the plumbing code.
2. I think you can avoid tracker
entirely now.
3. You probably don't want filesystem operations in the derivedStateOf
expression; those are intended to be very quick and that looks like a thread-blocking operation. Chances are you want something like this in StateModel:
var files by mutableStateOf<List<File>>(emptyList())
private set
// ...
suspend fun keepFilesUpdated() = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
snapshotFlow { path }
.mapNotNull {
File(it).listFiles()
?.filter { !it.isHidden && it.isFile }
?.toList()
?.sortedBy { it.name }
}
.collect { files = it }
}
and then call that method in a LaunchedEffect(state)
block similar to the other one that's there already. It's a little more complicated since now there's other async operations to manage the scope of, but LaunchedEffect
is pretty effective for that.nfrankel
01/09/2021, 11:15 AMtracker
is still necessary to let compose know that it should trigger the read from the FS again. if i remove it, it doesn’t work
3. i understand your point. i’ll check but for my current needs, it’s good enough
thanks again for your help