https://kotlinlang.org logo
Channels
100daysofcode
100daysofkotlin
100daysofkotlin-2021
advent-of-code
aem
ai
alexa
algeria
algolialibraries
amsterdam
android
android-architecture
android-databinding
android-studio
androidgithubprojects
androidthings
androidx
androidx-xprocessing
anime
anko
announcements
apollo-kotlin
appintro
arabic
argentina
arkenv
arksemdevteam
armenia
arrow
arrow-contributors
arrow-meta
ass
atlanta
atm17
atrium
austin
australia
austria
awesome-kotlin
ballast
bangladesh
barcelona
bayarea
bazel
beepiz-libraries
belgium
benchmarks
berlin
big-data
books
boston
brazil
brikk
budapest
build
build-tools
bulgaria
bydgoszcz
cambodia
canada
carrat
carrat-dev
carrat-feed
chicago
chile
china
chucker
cincinnati-user-group
cli
clikt
cloudfoundry
cn
cobalt
code-coverage
codeforces
codemash-precompiler
codereview
codingame
codingconventions
coimbatore
collaborations
colombia
colorado
communities
competitive-programming
competitivecoding
compiler
compose
compose-android
compose-desktop
compose-hiring
compose-ios
compose-mp
compose-ui-showcase
compose-wear
compose-web
confetti
connect-audit-events
corda
cork
coroutines
couchbase
coursera
croatia
cryptography
cscenter-course-2016
cucumber-bdd
cyprus
czech
dagger
data2viz
databinding
datascience
dckotlin
debugging
decompose
decouple
denmark
deprecated
detekt
detekt-hint
dev-core
dfw
docs-revamped
dokka
domain-driven-design
doodle
dsl
dublin
dutch
eap
eclipse
ecuador
edinburgh
education
effective-kotlin
effectivekotlin
emacs
embedded-kotlin
estatik
event21-community-content
events
exposed
failgood
fb-internal-demo
feed
firebase
flow
fluid-libraries
forkhandles
forum
fosdem
fp-in-kotlin
framework-elide
freenode
french
fritz2
fuchsia
functional
funktionale
gamedev
ge-kotlin
general-advice
georgia
geospatial
german-lang
getting-started
github-workflows-kt
glance
godot-kotlin
google-io
gradle
graphic
graphkool
graphql
graphql-kotlin
graviton-browser
greece
grpc
gsoc
gui
hackathons
hacktoberfest
hamburg
hamkrest
helios
helsinki
hexagon
hibernate
hikari-cp
hire-me
hiring
hiring-french
hongkong
hoplite
http4k
hungary
hyderabad
image-processing
india
indonesia
inkremental
intellij
intellij-plugins
intellij-tricks
internships
introduce-yourself
io
ios
iran
israel
istanbulcoders
italian
jackson-kotlin
jadx
japanese
jasync-sql
java-to-kotlin-refactoring
javadevelopers
javafx
javalin
javascript
jdbi
jhipster-kotlin
jobsworldwide
jpa
jshdq
juul-libraries
jvm-ir-backend-feedback
jxadapter
k2-early-adopters
kaal
kafka
kakao
kalasim
kapt
karachi
karg
karlsruhe
kash_shell
kaskade
kbuild
kdbc
kgen-doc-tools
kgraphql
kinta
klaxon
klock
kloudformation
kmdc
kmm-español
kmongo
knbt
knote
koalaql
koans
kobalt
kobweb
kodein
kodex
kohesive
koin
koin-dev
komapper
kondor-json
kong
kontent
kontributors
korau
korean
korge
korim
korio
korlibs
korte
kotest
kotest-contributors
kotless
kotlick
kotlin-asia
kotlin-beam
kotlin-by-example
kotlin-csv
kotlin-data-storage
kotlin-foundation
kotlin-fuel
kotlin-in-action
kotlin-inject
kotlin-latam
kotlin-logging
kotlin-multiplatform-contest
kotlin-mumbai
kotlin-native
kotlin-pakistan
kotlin-plugin
kotlin-pune
kotlin-roadmap
kotlin-samples
kotlin-sap
kotlin-serbia
kotlin-spark
kotlin-szeged
kotlin-website
kotlinacademy
kotlinbot
kotlinconf
kotlindl
kotlinforbeginners
kotlingforbeginners
kotlinlondon
kotlinmad
kotlinprogrammers
kotlinsu
kotlintest
kotlintest-devs
kotlintlv
kotlinultimatechallenge
kotlinx-datetime
kotlinx-files
kotlinx-html
kotrix
kotson
kovenant
kprompt
kraph
krawler
kroto-plus
ksp
ktcc
ktfmt
ktlint
ktor
ktp
kubed
kug-leads
kug-torino
kvision
kweb
lambdaworld_cadiz
lanark
language-evolution
language-proposals
latvia
leakcanary
leedskotlinusergroup
lets-have-fun
libgdx
libkgd
library-development
lincheck
linkeddata
lithuania
london
losangeles
lottie
love
lychee
macedonia
machinelearningbawas
madrid
malaysia
mathematics
meetkotlin
memes
meta
metro-detroit
mexico
miami
micronaut
minnesota
minutest
mirror
mockk
moko
moldova
monsterpuzzle
montreal
moonbean
morocco
motionlayout
mpapt
mu
multiplatform
mumbai
munich
mvikotlin
mvrx
myndocs-oauth2-server
naming
navigation-architecture-component
nepal
new-mexico
new-zealand
newname
nigeria
nodejs
norway
npm-publish
nyc
oceania
ohio-kotlin-users
oldenburg
oolong
opensource
orbit-mvi
osgi
otpisani
package-search
pakistan
panamá
pattern-matching
pbandk
pdx
peru
philippines
phoenix
pinoy
pocketgitclient
polish
popkorn
portugal
practical-functional-programming
proguard
prozis-android-backup
pyhsikal
python
python-contributors
quasar
random
re
react
reaktive
realm
realworldkotlin
reductor
reduks
redux
redux-kotlin
refactoring-to-kotlin
reflect
refreshversions
reports
result
rethink
revolver
rhein-main
rocksdb
romania
room
rpi-pico
rsocket
russian
russian_feed
russian-kotlinasfirst
rx
rxjava
san-diego
science
scotland
scrcast
scrimage
script
scripting
seattle
serialization
server
sg-user-group
singapore
skia-wasm-interop-temp
skrape-it
slovak
snake
sofl-user-group
southafrica
spacemacs
spain
spanish
speaking
spek
spin
splitties
spotify-mobius
spring
spring-security
squarelibraries
stackoverflow
stacks
stayhungrystayfoolish
stdlib
stlouis
strife-discord-lib
strikt
students
stuttgart
sudan
swagger-gradle-codegen
swarm
sweden
swing
swiss-user-group
switzerland
talking-kotlin
tallinn
tampa
teamcity
tegal
tempe
tensorflow
terminal
test
testing
testtestest
texas
tgbotapi
thailand
tornadofx
touchlab-tools
training
tricity-kotlin-user-group
trójmiasto
truth
tunisia
turkey
turkiye
twitter-feed
uae
udacityindia
uk
ukrainian
uniflow
unkonf
uruguay
utah
uuid
vancouver
vankotlin
vertx
videos
vienna
vietnam
vim
vkug
vuejs
web-mpp
webassembly
webrtc
wimix_sentry
wwdc
zircon
Powered by
Title
n

nfrankel

01/04/2021, 2:17 AM
hello getting my feet wet with compose for desktop how do you subscribe to changes in
MutableState
? (i’ve close to zero experience in android)
k

kenkyee

01/04/2021, 2:39 AM
You don't really subscribe to them... They just get used...
n

nfrankel

01/04/2021, 2:40 AM
ok but how do i handle the use-case where i want a variable updated when other variables are changed?
k

kenkyee

01/04/2021, 2:41 AM
I'm looking at your example and don't think it's a normal thing to do... You'd probably move that stuff into a viewmodel and do the normal coroutine thing with sharedflows
The auto change handling magic is for UI
n

nfrankel

01/04/2021, 2:42 AM
viewmodel
seems to be an android thing 😅
k

kenkyee

01/04/2021, 2:42 AM
MVI/Redux then
You could have UI events change the remember mutable state but unless it's a self contained widget, it's mixing business logic with UI...
n

nfrankel

01/04/2021, 2:46 AM
funny... the observer pattern is in the stdlib via
delegate by observable
i just want binding between two variables but because compose seems to frown on classes, i’m stuck 🤔
a

Adam Powell

01/04/2021, 2:46 AM
derivedStateOf
might be what you're looking for if you want snapshot state that depends on other snapshot state
compose doesn't frown on classes; you can write classes that back their fields with snapshot state
n

nfrankel

01/04/2021, 2:48 AM
@Adam Powell thanks for the hint about
derivedStateOf
the class thing is what i’m deducing after a couple of hours perhaps i’m completely mistaken
a

Adam Powell

01/04/2021, 2:50 AM
definitely open to feedback on how to better position some of the docs; most of them are trying to showcase the compose-specific concepts
for example, you can write classes like this:
class MyState(
  initialFoo: Foo
) {
  var foo by mutableStateOf(initialFoo)
  val bar by derivedStateOf { foo.computeBar() }
}
n

nfrankel

01/04/2021, 2:52 AM
i come from a swing background... from what i see, the idea is to let android developers move to the desktop
wrong persona for me 😉
a

Adam Powell

01/04/2021, 2:54 AM
yes, much of the docs are indeed from the perspective of Android developers so far 🙂
👍 1
independent of platform though, the intention is for
@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 scales
and that state often ends up looking like the snapshot state-backed example class above
if you would like to observe snapshot state changes from outside of compose APIs, there is
snapshotFlow {}
n

nfrankel

01/04/2021, 2:58 AM
the hoisting stuff is completely different from what i’m used to i believe it will take some time to wrap my head around it
a

Adam Powell

01/04/2021, 2:59 AM
yes, it's a very different way of thinking about UI than Swing
(or Android Views, for that matter)
it solves a lot of long-standing challenges though, especially around async responses and validation
often lends itself to easier testability
n

nfrankel

01/04/2021, 3:03 AM
i need to read more about
@Composable
i think it’s the main building block
the fact that most of compose documentation uses android doesn’t help for sure
a

Adam Powell

01/04/2021, 3:05 AM
You might find some of Leland's blog posts helpful, he writes mostly from a platform-agnostic perspective about the compose runtime itself, which all applies to compose desktop: https://medium.com/androiddevelopers/understanding-jetpack-compose-part-1-of-2-ca316fe39050
👍 1
n

nfrankel

01/04/2021, 3:06 AM
thanks for your help!
a

Adam Powell

01/04/2021, 3:07 AM
you're welcome! Happy to 🙂
n

nfrankel

01/04/2021, 3:07 AM
Let’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.
😂
a

Adam Powell

01/04/2021, 3:07 AM
it's a short section on that part, I promise 🙂
👍 1
n

nfrankel

01/04/2021, 3:20 AM
so the framework will actually call a
@Composable
function if any of its
@Composable
parameters has been changed correct?
c

Colton Idle

01/04/2021, 7:11 AM
Reading some docs on react (like lifting state up) has helped me conceptually get prepared for compose. if you wish to take a look at those docs. They are really good!
k

kenkyee

01/04/2021, 11:25 AM
https://kotlinlang.slack.com/archives/C01D6HTPATV/p1609730449296100?thread_ts=1609726620.287000&cid=C01D6HTPATV Yes... Think of it as rerendering children as needed and every param/remember is "state". It very much is like React and Flutter.
n

nfrankel

01/04/2021, 11:47 AM
Yes
Thanks 🙂
It very much is like React and Flutter
well, those references are lost on me as i know none of them 😅
k

kenkyee

01/04/2021, 12:04 PM
React and Flutter are much more mature though so the same concepts usually apply. E.g. they work best with MVI/Redux architectures to manage state.
c

Colton Idle

01/04/2021, 1:51 PM
@nfrankel I don't know react either. Just saying their docs are mature and super simple. So it's worth taking a peek there.
☝️ 1
👍 1
n

nfrankel

01/04/2021, 1:52 PM
i managed to get something working though i’m not sure whether it’s “the way”
a

Adam Powell

01/04/2021, 6:44 PM
happy to give some feedback on idiomatic patterns if you'd like to post a gist or similar
n

nfrankel

01/04/2021, 6:47 PM
i appreciate the proposal it’s a bit more than a gist but not gigantic either just a simple batch file renamer https://github.com/ajavageek/renamer-compose
thanks in advance
👍 1
a

Adam Powell

01/04/2021, 6:53 PM
by and large I'd say you're getting the hang of it. There's the style nit of
@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 🙂
🎉 1
Structurally I think you're seeing the design pressure that leads to
by mutableStateOf
-backed classes
composable functions that accept
MutableState<T>
parameters are usually a canary that signals that you want to establish a more specific API with the UI element
applyButton
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#L17
Some of the work with the file chooser using swing directly is a good example of things that can be wrapped into the declarative style and then used that way, but there's nothing inherently wrong with doing it the way it's done here. It's very similar to using
suspendCancellableCoroutine
to bridge
suspend
functions and callback-driven async code
the equivalent APIs for bridging such things in compose are the
*Effect
composables:
SideEffect
,
DisposableEffect
and
LaunchedEffect
, depending on what you're up to
👀 1
What is the
changed
state intending to model?
Some of the
SwingPanel
bits look suspicious but I think that might be on our end rather than yours 🙂
n

nfrankel

01/04/2021, 8:08 PM
• swing: i don’t see any table component, so i had to use the swing one • same for filechooser •
changed
is interesting
i want a “refresh” feature something that tells the framework to recall everything: the table needs to read from the fs again
i tried to achieve that by using “derived” mutable state but it seems those functions are not called
a

Adam Powell

01/04/2021, 10:10 PM
It looks like you don't need mutable/mutableStateLists in there, as they're only being consumed downstream
so I don't think the `.toMutableStateList()`s are heading in the direction you want
You might be looking for something more like:
val 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)
            }
        }
    }
}
so you get one
State<List<File>>
instance that will automatically update whenever the things it reads change
though I think there might be something structurally problematic with the
SwingPanel
API being used to present the file table, and I'm having trouble locating the source code for it to verify
from the call site it's accepting direct parameters of initialized-once Swing components; this means if the content lambda of the containing
Row
recomposes, new swing components are created instead of updating existing ones, which seems suspect
for this to work the way you're intending, it looks like you would need to observe snapshot changes of your list and notify your AbstractTableModel listeners of the changes
a good illustration of the kinds of things compose aims to do transparently 🙂
n

nfrankel

01/05/2021, 10:37 AM
i was missing
derivedStateOf
thanks for letting me know 👍
though I think there might be something structurally problematic with the 
SwingPanel
 API being used to present the file table, and I’m having trouble locating the source code for it to verify
😬
it looks like you would need to observe snapshot changes of your list and notify your AbstractTableModel listeners of the changes
so 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 files
any hint for that would be greatly appreciated (if you still have time of course)
a

Adam Powell

01/05/2021, 3:37 PM
If you connect a
snapshotFlow { theTableState }
collect call to send
AbstractTableModel
data change notifications I think you should be in business
👀 1
n

nfrankel

01/06/2021, 8:44 AM
i’ll have a look, thanks
i’m afraid i don’t manage to get this... so far, i’ve: •
[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 refresh
a

Adam Powell

01/06/2021, 4:28 PM
you should be able to do the same here if you want refresh to be manual
I presumed that you wanted the table to refresh automatically as the files change
the value of a
derivedStateOf
state object will always be up to date for instantaneous reads, like you would do from a button click handler
n

nfrankel

01/06/2021, 5:04 PM
you should be able to do the same here if you want refresh to be manual
do 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 change
that’s the case, i just don’t understand how
a

Adam Powell

01/06/2021, 7:48 PM
Since
AbstractTableModel
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)
}
n

nfrankel

01/06/2021, 8:24 PM
thanks @Adam Powell i’ll look at it tomorrow (i’m in europe) i really appreciate all the time you give me 🙇‍♂️
👍 1
i’ve updated the code according to your snippet good stuff: i don’t need to move out of the replacement field to refresh the table still, it doesn’t handle the initial issue when i click on the button, it rename files so the “source” of any change is the filesytem itself which is of course out of the scope of compose from the button, is there any way to call the compose refresh behavior?
sorry, it seems i don’t get it 😞
a

Adam Powell

01/07/2021, 3:58 PM
which data do you want to refresh from the button?
n

nfrankel

01/07/2021, 5:57 PM
the button rename the files so i want
files
to be refreshed
candidates
is derived data, so it will be updated automatically
and
files
is getting the children of the
path
which doesn’t change by itself
as a brute-force option, is there a function to tell compose to re-compose?
i managed to get it working by using a dummy variable, forcing recomposition
🎉
a

Adam Powell

01/07/2021, 6:23 PM
there is a function to tell it to recompose a scope, but reaching for it usually means there's a better way to structure the data flow.
@Composable fun MyComposable() {
  val recomposeMe = invalidate // returns an invalidator for this current scope
  Button(onClick = { recomposeMe() }) { /* ... */ }
}
is how you use it.
👍 1
This is again where I would define a class with properties backed
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.
☝️ 1
n

nfrankel

01/07/2021, 6:27 PM
now i think i understand 🙂
🎉 1
👍 1
k

kenkyee

01/07/2021, 11:22 PM
It'd help to read up on Redux or MVI architectures
👍 1
n

nfrankel

01/08/2021, 5:02 AM
@Adam Powell i’ve pushed what i believe is a final version of the code following your advices feedback is welcome 🙂
a

Adam Powell

01/08/2021, 10:00 PM
Took a look, a few things: 1. All of the custom get/set property methods in StateModel can be skipped thanks to property delegation. There's an IDE bug that can hinder auto-import for
androidx.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.
n

nfrankel

01/09/2021, 11:15 AM
1. good catch about the import! i didn’t understand why the samples didn’t work 😅 2.
tracker
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