Greetings! I have a question that I can't seem to ...
# compose
d
Greetings! I have a question that I can't seem to find an answer to in the documentation. How does Compose treat local functions and variables of function types inside Composable functions? Do we have to
remember
them? Or is there some optimization already built in? Here is some specific code:
Copy code
@Composable
private fun SomeComposable() {
    val fun1: (int: Int) -> String = { int ->
        int.toString()
    }

    fun fun2(int: Int): String {
        return int.toString()
    }

    val fun3: (int: Int) -> String = remember {
        { int ->
            int.toString()
        }
    }
    
    fun1(5)
    fun2(5)
    fun3(5)
}
What would be the most optimal approach between
fun1
,
fun2
and
fun3
?
a
inlined fun2
a
For functions without any dependencies such as those in your example, you should define them as top-level functions. For functions with dependencies (captured values), if the dependencies are all stable and didn't change, the same lambda instance will be reused.
d
@andylamax I don't think local inline functions are supported yet
I guess a better example would be this
Copy code
@Composable
private fun SomeComposable(param: Int) {
    val fun1: () -> String = { 
        param.toString()
    }

    fun fun2(): String {
        return param.toString()
    }

    val fun3: () -> String = remember {
        {
            param.toString()
        }
    }

    fun1()
    fun2()
    fun3()
}
In this case fun1 and fun2 are totally ok, is that correct?
a
I've edited my previous message to add that the captured values mustn't change in order for the lambda instance to be reused. In your example, if
param
changes, the
fun2
instance will be recreated. Also note that
fun3
is broken because it only captures the initial
param
value even if
param
changes.
a
still, your functions don't capture anything (i.e. they can live outside of your composable) and it would work, i.e.
Copy code
val fun1: (int: Int) -> String = { int ->
    int.toString()
}

fun fun2(int: Int): String {
    return int.toString()
}

val fun3: (int: Int) -> String = remember {
    { int ->
        int.toString()
     }
}

@Composable
private fun SomeComposable() {
    fun1(5)
    fun2(5)
    fun3(5)
}
See??
d
@andylamax Yes, I realize the initial example is bad 🙂 I provided a better one. The point is usage of local functions in general, not a specific example
a
Oooh, my bad. I missed the recorrection
to answer your question, func2 is better than others (notice how I have refrained myself from calling it performant than others)
d
@Albert Chang But that's a good thing, right? So if I undestand correctly, it is able to trace when values are changed and only then recreate the lambda. Basically doing the same thing as
remember(param)
@andylamax Could you please explain why? Off topic - I'm new to Slack, is there a way to replay to a message? Or is tagging ok?
a
Yeah that's right.
a
Tagging is okay, My reasoning is,
fun3
is bad coz it is capturing an old value and it kinda goes off sync when param changes and might leak memory
fun1
would behave exactly like
fun2
(atleast on jvm), but
fun2
is less verbose (which makes it the best candidate)
I would like to suggest a better approach though (if you don't mind)
Copy code
@Composable
fun Fun4(param: Int) {
   param.toString()
}

@Composable
private fun SomeComposable(param: Int) {
   Fun4(param)
}
Compose will take care of either recomposing it (if param changes) or not. You won't have to worry at all
d
I don't mind at all, it is very much appreciated! Yes, that makes sense for simple functions, but often the local function operates with a ton of different parameters, so we would need to create a top-level function with all of them if we try to move it outside, which is not always convenient. Is there any difference between Fun4 and fun1/fun2, then? In terms of performance. And by the way, the same thing is true for state objects, right? Something like
Copy code
var param by remember {
    mutableStateOf(0)
}

val fun1: () -> String = {
    param.toString()
}

param = 5
fun1()
fun1()
will be 0 and then 5, correct?
a
Only use composable function if you need to call other composable functions. Composable function is not a way of optimization.
And what do mean by
the same thing
?
d
So what you're saying is Fun4 in this case will be less performant?
The same thing as with captured values. So fun1 will be recreated when param changes?
a
No. What it captues is the
MutableState
, which will never change.
a
If Fun4 is being compared to the capturing functions, I would disagree that it would be less performant. But if it is being compared to other non capturing, non Composable funcitons, then it would be less performat
d
@Albert Chang Even if we use a delegate (
by remember
), like in the example?
a
To add here, if the capturing functions will be capturing a
MutableState
, then yes,
Fun4
will be less performant. But if it would be capturing parameters (as in the examples), it will be more performant that its capturing counterparts
d
@Albert Chang Oh, ok, so fun1 will never be recreated of course, as the state object is still the same. But will it be invoked again? As we are reading from a state object inside. Or is that true only for Composable functions, like
Copy code
val fun1: @Composable () -> String = {
    param.toString()
}
a
That’s the thing - you can always make a composable function that doesn’t call other composable functions more efficient by converting it to a normal function.
State reads don’t need to be in composable function in order for the observation to work.
d
Oh, so simple
fun1: () -> String
will work as expected then?
Great, now what about local Composable functions
Copy code
@Composable
fun SomeComposable(param: String) {
    @Composable
    fun fun5() {
        Text(text = param)
    }

    val fun6: @Composable () -> Unit = {
        Text(text = param)
    }
    
    fun5()
    fun6()
    fun7(param)
}

@Composable
fun fun7(param: String) {
    Text(text = param)
}
Is there any significant penalty when using fun5/fun6 intead of fun7? Or it is the same as for normal functions, so pretty much no difference?
a
The same.
d
Ok, thank you very much!