https://kotlinlang.org logo
#compose
Title
# compose
a

aballano

11/01/2019, 4:12 PM
Hi there, I'm currently doing a small pet project and trying out compose. For what I've seen so far, also including the JetNews app in google samples repo, it seems that the way of using Models is by putting them in some global scope and accessing them from the calling site + modifying with mutable states. Is this the intended behaviour? Or would it be possible to use a more direct functional/reactive approach of passing updated states from an upstream?
l

Luca Nicoletti

11/01/2019, 4:13 PM
g

Giorgos Neokleous

11/01/2019, 4:14 PM
Dammit you beat me to it @Luca Nicoletti 🐢
l

Luca Nicoletti

11/01/2019, 4:14 PM
That’s because I’m here waiting for a feedback on my questions 😂
a

aballano

11/01/2019, 4:16 PM
thank you @Luca Nicoletti that's helpful!
l

Luca Nicoletti

11/01/2019, 4:16 PM
👍🏼
a

aballano

11/01/2019, 4:18 PM
Although I'm wondering @Luca Nicoletti, how would you make a change afterwards, let's say when a button is clicked you want to load some data or perform a network op that comes back as state change? would you need to call to setContent again?
l

Leland Richardson [G]

11/01/2019, 4:23 PM
please do not use globals
not sure why some of our examples do this.
🙌 3
t

themishkun

11/01/2019, 4:24 PM
@aballano You can access all properties visible in your lexical scope. So you can directly access properties of enclosing class or pass lambdas for listeners in your components
👍 3
l

Leland Richardson [G]

11/01/2019, 4:24 PM
holding onto a reference from an activity or wherever you are starting the root of the hierarchy is a fine approach
you can also do it more locally
l

Luca Nicoletti

11/01/2019, 4:25 PM
@aballano You provide a callback to your component
l

Leland Richardson [G]

11/01/2019, 4:25 PM
which i think scales better as your application grows
so you can do
var x = +memo { MyModel(...) }
and x will be the same across recompositions
this is basically what
+state
is
a

aballano

11/01/2019, 4:27 PM
do you need to wrap a @Model annotated class with memo or state or the annotation does it for you already?
l

Leland Richardson [G]

11/01/2019, 4:28 PM
val x = +state { 0 }
is basically shorthand for
val x = +memo { State(0) }
where
State
is a value holder class annotated with @Model
memo is what enables you to “hold on to” or “remember” the value across recompositions of the function
the @Model part is what enables the runtime to observe reads/mutations to that class
m

Michal Bacik

11/01/2019, 4:47 PM
Maybe later we get also coroutines natively in composables? So that we can load data async where needed, and be sure it's auto cancelled when UI hierarchy is destroyed...
l

Leland Richardson [G]

11/01/2019, 4:49 PM
yes, precisely
suspend functions don’t currently work with the IR compiler so we are holding off on this
but i have a bunch of code queued up for when this is fixed 🙂
you can also do this like right now without coroutines and instead just with callback-based async functions
l

Luca Nicoletti

11/01/2019, 4:51 PM
callback-hell anyone? 🧌
a

aballano

11/01/2019, 4:51 PM
wonderful, thank you for the info,
memo
is what I was missing @Leland Richardson [G] Now it's working with lazy val + setting mutable variables inside the model
val mainModel by lazy { +model { MainModel() } }
l

Leland Richardson [G]

11/01/2019, 4:54 PM
hmm
l

Luca Nicoletti

11/01/2019, 4:54 PM
Run @aballano, run! You just triggered @Leland Richardson [G]
🤣 1
l

Leland Richardson [G]

11/01/2019, 4:54 PM
so this works, but only because the temporary
unaryPlus
operator is global, and will not work once we take it away (which will be very soon)
basically, think of
+
as something you can only use inside of a composable function
l

Luca Nicoletti

11/01/2019, 4:55 PM
Can we please have an announcement
here
in the channel when the
+
will be removed?
l

Leland Richardson [G]

11/01/2019, 4:55 PM
if you paste your code here i could show you the way i would write it, that will be valid when we take away the + operator
@Luca Nicoletti I will shout from the rooftops
l

Luca Nicoletti

11/01/2019, 4:56 PM
Will I be able to hear from London? 🧌
l

Leland Richardson [G]

11/01/2019, 4:56 PM
my voice carries 😉
l

Luca Nicoletti

11/01/2019, 4:56 PM
Or will I see photos of you wasted after the party? 😂
a

aballano

11/01/2019, 4:56 PM
sure, let me cleanup and post a code snippet 😉
l

Leland Richardson [G]

11/01/2019, 5:09 PM
i see. in the current usage, i don’t think memo should be needed, since you’re using the Activity to hold the reference
so doing:
Copy code
data class Message(
  val sender: String,
  val message: String
)

@Model
data class MainModel(
  var messages: List<Message> = emptyList()
)

class MainActivity : AppCompatActivity() {
  private val algebra = MainAlgebra()
  private val mainModel = MainModel()
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
      mainContent(mainModel, ::onButtonClicked)
    }
  }
  private fun onButtonClicked() {
    algebra.getMessages().continueOn(UIContext).unsafeRunAsync { cb ->
      cb.fold(
        ifRight = {
          mainModel.messages = mainModel.messages + it
        },
        ifLeft = { throw it }
      )
    }
  }
}
should be just fine
l

Luca Nicoletti

11/01/2019, 5:10 PM
Is
List
now working? 🙂 Isn’t
ModelList
the preferred one?
l

Leland Richardson [G]

11/01/2019, 5:11 PM
this is probably closer to what i would do, and is an alternative:
Copy code
data class Message(
  val sender: String,
  val message: String
)

class MainActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
      mainContent()
    }
  }
}

@Composable fun mainContent() {
  val algebra = +memo { MainAlgebra() }
  val messages by +state<List<Message>> { emptyList() }

  fun onButtonClicked() {
      algebra.getMessages().continueOn(UIContext).unsafeRunAsync { cb ->
        cb.fold(
          ifRight = {
            messages = messages + it
          },
          ifLeft = { throw it }
        )
      }
    }
  }

  // whatever you were doing before...

}
List always worked
@Model is shallow observation, so properties of a model class should be considered immutable values
a

aballano

11/01/2019, 5:13 PM
uhm, that's an interesting approach, I'll check out, thanks again @Leland Richardson [G]
l

Leland Richardson [G]

11/01/2019, 5:13 PM
so doing like you did,
messages = messages + it
works just fine
modelList is if you want a mutable list implementation where the mutations will be observed by the same mechanisms as other property assignments would in @Model
IMO ModelList should be considered an advanced case, if you know you want to have a mutable list
and instead immutable lists should be the first go-to
and by immutable lists, i just mean
List
though if you have some shared persistent data structure library in kotlin that you want to use, then even better 🙂
a

aballano

11/01/2019, 6:28 PM
in your example with the inmutable list, it should then be
var
, right? otherwise how do you tigger an update without mutability?
ah, saw it
val (messages, messagesChanges) = +state<List<Message>> { emptyList() }
l

Leland Richardson [G]

11/01/2019, 6:33 PM
yeah that was my mistake
var
if you’re using it as a property delegate
👍 1
a

aballano

11/01/2019, 6:37 PM
@Leland Richardson [G] what would be the scope of that function's memo, I assume is activity's? in the case of long running ops i guess should be enough by canceling in onDestroy, right? just like before
l

Leland Richardson [G]

11/01/2019, 6:39 PM
the memo is alive as long as the function is part of the hierarchy
the integration with activities should be able to be more automatic
but right now, you should call
disposeComposition
in onDestroy
and compose things should get properly cleaned up then
if you want to do some cleanup operations locally in a compose function, you can use
+onDispose { ... }
a

aballano

11/01/2019, 6:41 PM
great, thanks 👍
l

Leland Richardson [G]

11/01/2019, 6:41 PM
note that often a cleanup operation is often tied to an “initialize” or “subscribe” operation
the way this is typically done is with
+onCommit { ... }
and
onCommit
has its own
onDispose
so for instance
Copy code
+onCommit {
  myThing.subscribe(...)
  onDispose { myThing.unsubscribe(...) }
}
onCommit is called every composition though, so this may not be quite what you want
in this case, if
myThing
was a parameter
you could do
Copy code
+onCommit(myThing) {
  myThing.subscribe(...)
  onDispose { myThing.unsubscribe(...) }
}
and then this will only call the body of
onCommit
whenever
myThing
changes
this is a varargs thing so you can add as many “inputs” to onCommit as you want
this is analogous to inputs of a memoization
and then you can use
onActive
which happens on the first composition only. This is equivalent to
onCommit(someConstant)
also, i’m curious, but why in
Copy code
mainContent(MainModel(messages), ::onButtonClicked)
did you wrap
messages
in
MainModel
?
a

aballano

11/01/2019, 6:49 PM
ah, just a detail, i had more parameters in MainModel to render the entire UI, so I wrapped everything
l

Leland Richardson [G]

11/01/2019, 6:54 PM
i see
in this case, you could just use that model to begin with, instead of state
val model = +memo { MainModel() }
then
model.messages = messages + it
no need to separate them, i only changed it to state because in your example it was only a single value
a

aballano

11/01/2019, 6:59 PM
cool, i'll keep that in mind when it grows 🙂
👍 1
l

Leland Richardson [G]

11/01/2019, 7:02 PM
if you have both the state and the model, then you’re creating a new model every time messages changes, which isn’t really utilizing model at all
at that point it might as well just be a normal class
l

Luca Nicoletti

11/01/2019, 7:03 PM
TL;DR;
do you need to wrap a
@Model
annotated class with
memo
or
state
or the annotation does it for you already?
Answer is yes?
a

aballano

11/01/2019, 7:06 PM
If i understood correctly, either you do
+memo { MyModel("text") }
or
+state { "text" }
if you don't need the MyModel wrapper
l

Leland Richardson [G]

11/01/2019, 7:06 PM
yes, unless something else is holding onto the @Lucas Modesto
🤔 1
@Model
l

Luca Nicoletti

11/01/2019, 7:06 PM
Correct
l

Leland Richardson [G]

11/01/2019, 7:06 PM
(lol, autocomplete)
😂 1
l

Luca Nicoletti

11/01/2019, 7:07 PM
+state
wrap the value you provide in a
State
instance, which is a
@Model
annotated class
☝️ 2
l

Leland Richardson [G]

11/01/2019, 7:07 PM
yep
l

Luca Nicoletti

11/01/2019, 7:07 PM
Poor Luca Modesto
l

Leland Richardson [G]

11/01/2019, 7:07 PM
haha
a

aballano

11/01/2019, 7:07 PM
this was a very informative thread, thanks folks 🙂
👍 1
l

Luca Nicoletti

11/01/2019, 7:07 PM
Tagged into an infinite thread of knowledge
Good luck catching up 😛
Thanks @Leland Richardson [G], not me 😄