Compose: Proper way of extending the RowScope Modifier? (Or any Scope Modifier for that matter) - kotlin

I'm creating a Row of Text Composables, imagine it like a table row.
Image: https://i.stack.imgur.com/jR3oB.png
If you care for the backstory:
The amount of "cells" of my custom Row Composable is dynamic and I use Box Composables to wrap the Text in the "cells". I need to apply different Modifier parameters to each Box, which I first tried to achieve via creating a Modifier extension.
Code of my Modifier extension:
#SuppressLint("ComposableModifierFactory", "ModifierFactoryExtensionFunction") // I wish I could. The RowScope requirement makes doing it the easy way impossible
#Composable
fun RowScope.rowOfTextCellsBoxModifier(
index: Int,
columnWidths: ArrayList<Dp?>,
columnWeights: ArrayList<Float?>
): Modifier {
#Suppress("RedundantExplicitType") // bull**** (compose kotlinCompilerExtensionVersion 1.3.2)
var returnModifier: Modifier = Modifier
if (index != columnWidths.size - 1)
returnModifier = returnModifier.then(Modifier.verticalEndLine())
returnModifier = if (columnWidths[index] != null)
returnModifier.then(Modifier.width(columnWidths[index]!!))
else {
val weight = try {
columnWeights[index]
} catch (ignored: Throwable) {
1f
}
returnModifier.then(Modifier.weight(weight!!))
}
return returnModifier
}
I moved on to just generating the entire Box in a #Composable function, but that's beside the point ;)
As you can see, I want to add a Modifier.weight() at one point.
However, .weight() is a Modifier function that is only applicable in certain Scopes, like in RowScope. Which means that my "Modifier extension" needs to apply to RowScope, which turned out impossible to find instructions on.
So, imagine I hadn't found a better solution:
What would be the proper way of extending the RowScope Interface's Modifier?
Usually, when you need to create a Composable that is applicable to certain scopes, you write something like:
RowScope.YourComposable() {...}
And when you extend Modifier, you write:
Modifier.yourExtension() {...}
But that does not work for Scoped Modifiers:
RowScope.Modifier.YourModifierExtension() {...}
is invalid code.

Related

How to use remember & mutableStateOf seperately?

Usually, compose codes are like
#Preview
#Composable
fun BuildMyView() {
val counter = rememberSaveable { mutableStateOf(1) }
Text(
modifier = Modifier
.fillMaxSize()
.wrapContentSize(align = Alignment.Center)
.clickable { counter.value++ },// click and text will ++
text = "${counter.value}"
)
}
Recently, I want to collect all view data together and build a state and create sth like:
data class MyState(
val data: MutableState<String>
)
val stateTemp = MyState(mutableStateOf("hello"))
#Preview
#Composable
fun BuildMyView() {
val counter = rememberSaveable { stateTemp.data}
Text(
modifier = Modifier
.fillMaxSize()
.wrapContentSize(align = Alignment.Center)
.clickable { stateTemp.data.value += "-1" },
text = counter.value
)
}
In second case, when I click text, it does ++ but if I go to a new page and come back, all changes will lost however in first case it doesn't.
I then read some compose codes and get confusing since I didn't find where remember subscribe a mutable state.
Is there a method to make mutable state out of compose work?
Beside, is somewhere I can find codes generated by compose under my gradle build dir or anywhere else (except dex, that's too hard to read)? Compose really did amazing job but I cannot read the real codes running and that makes much more difficult for freshman to get start.
UPDATE ON 2022/6/15
Now I found a proper solution to use viewmodel instead of state holder and use mutableState in viewmodel and subscribe state in view composable part so that I can avoid complex grammar of viewmodel with livedata. Hope that will help followers.
I then read some compose codes and get confusing since I didn't find where remember subscribe a mutable state.
As far as I understand, remember calculates and caches what ever is inside its lambda during the first composition or first execution of the #composable function only, this is the only thing that the #composable remembers or subscribes on. Unless you provide a key to a remember, that when it changed, it will trigger a re-calculation to be remembered
Is there a method to make mutable state out of compose work?
Do you mean some piece of non composable code or function that will automatically trigger when this State object is changed? If this is what you mean, I suppose you just have to resort back to good old observable patterns
As far as I understand, compose States are specifically designed to work with the underlying Snapshot system where the heart of triggering the composition mechanism happens, I can't imagine (so far) any usage of State objects outside of composition or outside of the Snapshot system.
In second case, when I click text, it does ++ but if I go to a new page and come back, all changes will lost however in first case it doesn't.
As for using rememberSaveable, I would be careful using it , it might look the same as remember with just an additional power of saving/restoration, but it has more power with equal responsibilities imposed to it that you have to take into account when using it. I haven't used or defined a rememberSaveable object without defining a Saver in it explicitly, so I can only assume in your case here
rememberSaveable { mutableStateOf(1) }
the composable observes a rememberSaveable that also observes an actual object that actually changes and implicitly saves and restores that object with the most recent value
while in this, I think
data class MyState(
val data: MutableState<String>
)
val stateTemp = MyState(mutableStateOf("hello"))
...
...
rememberSaveable { stateTemp.data}
rememberSaveable saves the state of the val data: MutableState<String> (which is empty), while the composable observes an instance of a mutableState that is changing, unfortunately rememberSaveable already saved an initial state in way like this rememberSaveable { data: MutableState<String> } not the actual mutableStateOf("hello") that changes, so it will restore it that way when you go back.
I'm curious, you can try implementing your MyState class with a companion object holding a Saver where you can define how rememberSaveable will Save and Restore the data, I think it will restore it when you navigate back to that screen. When you debug a Saver implementation, you will also notice that the restoration is invoked during recomposition. Im not quite sure though
Disclaimer: Im just learning compose recently and still digging deeper about the Snapshot system, and it seemed like you're heading in the same direction as I do. I'd recommend to visit this link once in a while if your'e interested in the Snapshot system. Apologies as well, I can't comment yet due to lack of reputation so I just posted my thought and current understanding of how State and composition work together.

How to use Jetpack Compose Material theme color inside Kotlin enum class? [duplicate]

I wish to provide a parameter to a #Composable function that accepts a color, but only one from a discrete list.
Something along the lines of this:
enum class SpecialColor(val color: Color) {
ALPHA(MaterialTheme.colors.onSurface),
BETA(MaterialTheme.colors.onSecondary)
}
#Composable
fun ColorSample(specialColor: SpecialColor) {
Box(
modifier = Modifier
.width(100.dp)
.height(100.dp)
.background(specialColor.color)
)
}
#Preview
#Composable
fun PreviewSample() {
CustomTheme {
ColorSample(specialColor = SpecialColor.ALPHA)
}
}
As the above is referencing MaterialTheme.colors outside a composable context, the following error occurs:
#Composable invocations can only happen from the context of a
#Composable function
If a color is referenced directly, instead of via MaterialTheme, the color won't properly update for things like light/dark mode.
One tactic might be to map enumerated values to MaterialTheme colors within the #Composable function itself. But this is cumbersome, bulky, and doesn't scale well - imagine a much longer list of colors and the same SpecialColor list desired to be reused for many function.
Another tactic might be to modify the theme colors directly with LocalContentColor or similar, but this is too broad of a brush and will change colors for more than just the targeted function.
Returning a color from a utility #Composable function is also not an option, as #Composable functions don't have return values.
So...
Any thoughts on how to provide one of an enumerated list of Compose Material colors as a parameter?
In a clean and extendable way that scales well?
This is actually not true:
#Composable functions don't have return values.
You can annotate both function with return types and even getter only properties with #Composable. For example this is part of Material theme source code.
Here's how you can define your color:
enum class SpecialColor {
ALPHA,
BETA,
;
val color: Color
#Composable
#ReadOnlyComposable
get() = when(this) {
ALPHA -> MaterialTheme.colors.onSurface
BETA -> MaterialTheme.colors.onSecondary
}
}

How does a jetpack-compose composable become aware of its children?

This android tutorial has the below code snippet:
#Composable
private fun MyApp() {
Surface(color = MaterialTheme.colors.background) {
Greeting("Android")
}
}
My first thought was that the Surface composable gets its Greeting child by calling the lambda parameter containing the child and getting it back as a return value. But later in the tutorial we get this example:
#Composable
private fun Greeting(name: String) {
Surface(color = MaterialTheme.colors.primary) {
Column(modifier = Modifier.padding(24.dp)) {
Text(text = "Hello,")
Text(text = name)
}
}
}
Somehow the Column is aware of both the "Hello" Text and the name Text, even though calling the lambda would only give the name Text as a return value.
So my question is: what is the mechanism that makes a Composable aware of its children?
Similarly to for example suspend functions, #Composable functions are processed by the compiler in a very special way. This is to allow automatic recompositions and also to implicitly pass the context between components.
Documentation for #Composable specifies:
A useful mental model for Composable functions is that an implicit "composable context" is passed into a Composable function, and is done so implicitly when it is called from within another Composable function. This "context" can be used to store information from previous executions of the function that happened at the same logical point of the tree.
We can see it in action by writing a simple composable function and analyzing the resulting bytecode. Taking this source code:
#Composable
fun Foo() {
Text("foo")
}
We get the bytecode consisting of hundreds of instructions and some of the resulting code is even placed in a separate, synthesized class. We will focus on most important parts:
public static final void Foo(androidx.compose.runtime.Composer, int);
As we can see, our parameterless Foo() function actually implicitly receives a Composer and some integer.
3: invokeinterface #24, 2 // InterfaceMethod androidx/compose/runtime/Composer.startRestartGroup:(I)Landroidx/compose/runtime/Composer;
Another Composer object is acquired by calling startRestartGroup() on the received Composer.
55: invokestatic #73 // Method androidx/compose/material/TextKt."Text-fLXpl1I":(Ljava/lang/String;Landroidx/compose/ui/Modifier;JJLandroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/FontFamily;JLandroidx/compose/ui/text/style/TextDecoration;Landroidx/compose/ui/text/style/TextAlign;JIZILkotlin/jvm/functions/Function1;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/runtime/Composer;III)V
This is a call to Text(). It is hard to read due to the large number of optional parameters, but we can notice that it also receives a Composer object. This parameter is synthesized, we can't find it in the list of Text() parameters - similarly as in our Foo() function.
Foo() passes the composer acquired with startRestartGroup() to Text().
While I don't know the exact functionality and the meaning of the Composer, we can clearly see that Compose framework implicitly passes the context between composable functions, making possible to wire components together.

About Kotlin code style. When do we use field?.let { it.doSomething() } and when use field?.doSomething() directly

Recently I meet some problem about code style in Kotlin. I can't tell which code style is better.
Assume there are nullable field here:
var scoreView: TextView? = null
val bgImageView: ImageView? = null
And I'd like to write like:
fun foo() {
scoreView?.apply {
text = getScore()
textColor = getColor()
...
}
bgImageView?.apply {
Glide.with(context)
.load(xxx)
.into(this)
}
}
And my team leader want to change it to :
fun foo() {
scoreView?.text = getScore()
scoreView?.textColor = getColor()
...
Glide.with(context)
.load(xxx)
.into(bgImageView?:return)
}
I feel both are ok to me, but prefer the first one because I could write less 'xxView?.'
I wonder if there is some code style or rule about this. Or some common view about it.
Thanks.
According to Kotlin's official coding style, when you're calling multiple functions which primarily interact with one object, putting that code inside a scope function like .apply is the idiomatic approach. Of course, your workplace may use different conventions, so definitely ask your team leader about this.
In your first example, you're using apply for exactly the purpose it was designed: to set multiple properties on one object, and enhance readability in these situations.
scoreView?.apply {
text = getScore()
textColor = getColor()
...
}
In your second example, the apply function serves to separate code that acts on your nullable object bgImageView. This use is supported by the style guide, although it seems to recommend let more strongly in these nullable cases.
bgImageView?.apply {
Glide.with(context)
.load(xxx)
.into(this)
}
bgImageView?.let {
Glide.with(context)
.load(xxx)
.into(it)
}
The style guide also describes intended uses of the with, also, and run functions.

Example of when should we use run, let, apply, also and with on Kotlin

I wish to have a good example for each function run, let, apply, also, with
I have read this article but still lack of an example
All these functions are used for switching the scope of the current function / the variable. They are used to keep things that belong together in one place (mostly initializations).
Here are some examples:
run - returns anything you want and re-scopes the variable it's used on to this
val password: Password = PasswordGenerator().run {
seed = "someString"
hash = {s -> someHash(s)}
hashRepetitions = 1000
generate()
}
The password generator is now rescoped as this and we can therefore set seed, hash and hashRepetitions without using a variable.
generate() will return an instance of Password.
apply is similar, but it will return this:
val generator = PasswordGenerator().apply {
seed = "someString"
hash = {s -> someHash(s)}
hashRepetitions = 1000
}
val pasword = generator.generate()
That's particularly useful as a replacement for the Builder pattern, and if you want to re-use certain configurations.
let - mostly used to avoid null checks, but can also be used as a replacement for run. The difference is, that this will still be the same as before and you access the re-scoped variable using it:
val fruitBasket = ...
apple?.let {
println("adding a ${it.color} apple!")
fruitBasket.add(it)
}
The code above will add the apple to the basket only if it's not null. Also notice that it is now not optional anymore so you won't run into a NullPointerException here (aka. you don't need to use ?. to access its attributes)
also - use it when you want to use apply, but don't want to shadow this
class FruitBasket {
private var weight = 0
fun addFrom(appleTree: AppleTree) {
val apple = appleTree.pick().also { apple ->
this.weight += apple.weight
add(apple)
}
...
}
...
fun add(fruit: Fruit) = ...
}
Using apply here would shadow this, so that this.weight would refer to the apple, and not to the fruit basket.
Note: I shamelessly took the examples from my blog
There are a few more articles like here, and here that are worth to take a look.
I think it is down to when you need a shorter, more concise within a few lines, and to avoid branching or conditional statement checking (such as if not null, then do this).
I love this simple chart, so I linked it here. You can see it from this as written by Sebastiano Gottardo.
Please also look at the chart accompanying my explanation below.
Concept
I think it as a role playing way inside your code block when you call those functions + whether you want yourself back (to chain call functions, or set to result variable, etc).
Above is what I think.
Concept Example
Let's see examples for all of them here
1.) myComputer.apply { } means you want to act as a main actor (you want to think that you're computer), and you want yourself back (computer) so you can do
var crashedComputer = myComputer.apply {
// you're the computer, you yourself install the apps
// note: installFancyApps is one of methods of computer
installFancyApps()
}.crash()
Yup, you yourself just install the apps, crash yourself, and saved yourself as reference to allow others to see and do something with it.
2.) myComputer.also {} means you're completely sure you aren't computer, you're outsider that wants to do something with it, and also wants it computer as a returned result.
var crashedComputer = myComputer.also {
// now your grandpa does something with it
myGrandpa.installVirusOn(it)
}.crash()
3.) with(myComputer) { } means you're main actor (computer), and you don't want yourself as a result back.
with(myComputer) {
// you're the computer, you yourself install the apps
installFancyApps()
}
4.) myComputer.run { } means you're main actor (computer), and you don't want yourself as a result back.
myComputer.run {
// you're the computer, you yourself install the apps
installFancyApps()
}
but it's different from with { } in a very subtle sense that you can chain call run { } like the following
myComputer.run {
installFancyApps()
}.run {
// computer object isn't passed through here. So you cannot call installFancyApps() here again.
println("woop!")
}
This is due to run {} is extension function, but with { } is not. So you call run { } and this inside the code block will be reflected to the caller type of object. You can see this for an excellent explanation for the difference between run {} and with {}.
5.) myComputer.let { } means you're outsider that looks at the computer, and want to do something about it without any care for computer instance to be returned back to you again.
myComputer.let {
myGrandpa.installVirusOn(it)
}
The Way to Look At It
I tend to look at also and let as something which is external, outside. Whenever you say these two words, it's like you try to act up on something. let install virus on this computer, and also crash it. So this nails down the part of whether you're an actor or not.
For the result part, it's clearly there. also expresses that it's also another thing, so you still retain the availability of object itself. Thus it returns it as a result.
Everything else associates with this. Additionally run/with clearly doesn't interest in return object-self back. Now you can differentiate all of them.
I think sometimes when we step away from 100% programming/logic-based of examples, then we are in better position to conceptualize things. But that depends right :)
There are 6 different scoping functions:
T.run
T.let
T.apply
T.also
with
run
I prepared a visual note as the below to show the differences :
data class Citizen(var name: String, var age: Int, var residence: String)
Decision depends on your needs. The use cases of different functions overlap, so that you can choose the functions based on the specific conventions used in your project or team.
Although the scope functions are a way of making the code more concise, avoid overusing them: it can decrease your code readability and lead to errors. Avoid nesting scope functions and be careful when chaining them: it's easy to get confused about the current context object and the value of this or it.
Here is another diagram for deciding which one to use from https://medium.com/#elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84
Some conventions are as the following :
Use also for additional actions that don't alter the object, such as logging or printing debug information.
val numbers = mutableListOf("one", "two", "three")
numbers
.also { println("The list elements before adding new one: $it") }
.add("four")
The common case for apply is the object configuration.
val adam = Person("Adam").apply {
age = 32
city = "London"
}
println(adam)
If you need shadowing, use run
fun test() {
var mood = "I am sad"
run {
val mood = "I am happy"
println(mood) // I am happy
}
println(mood) // I am sad
}
If you need to return receiver object itself, use apply or also
let, also, apply, takeIf, takeUnless are extension functions in Kotlin.
To understand these function you have to understand Extension functions and Lambda functions in Kotlin.
Extension Function:
By the use of extension function, we can create a function for a class without inheriting a class.
Kotlin, similar to C# and Gosu, provides the ability to extend a class
with new functionality without having to inherit from the class or use
any type of design pattern such as Decorator. This is done via special
declarations called extensions. Kotlin supports extension functions
and extension properties.
So, to find if only numbers in the String, you can create a method like below without inheriting String class.
fun String.isNumber(): Boolean = this.matches("[0-9]+".toRegex())
you can use the above extension function like this,
val phoneNumber = "8899665544"
println(phoneNumber.isNumber)
which is prints true.
Lambda Functions:
Lambda functions are just like Interface in Java. But in Kotlin, lambda functions can be passed as a parameter in functions.
Example:
fun String.isNumber(block: () -> Unit): Boolean {
return if (this.matches("[0-9]+".toRegex())) {
block()
true
} else false
}
You can see, the block is a lambda function and it is passed as a parameter. You can use the above function like this,
val phoneNumber = "8899665544"
println(phoneNumber.isNumber {
println("Block executed")
})
The above function will print like this,
Block executed
true
I hope, now you got an idea about Extension functions and Lambda functions. Now we can go to Extension functions one by one.
let
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
Two Types T and R used in the above function.
T.let
T could be any object like String class. so you can invoke this function with any objects.
block: (T) -> R
In parameter of let, you can see the above lambda function. Also, the invoking object is passed as a parameter of the function. So you can use the invoking class object inside the function. then it returns the R (another object).
Example:
val phoneNumber = "8899665544"
val numberAndCount: Pair<Int, Int> = phoneNumber.let { it.toInt() to it.count() }
In above example let takes String as a parameter of its lambda function and it returns Pair in return.
In the same way, other extension function works.
also
public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }
extension function also takes the invoking class as a lambda function parameter and returns nothing.
Example:
val phoneNumber = "8899665544"
phoneNumber.also { number ->
println(number.contains("8"))
println(number.length)
}
apply
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
Same as also but the same invoking object passed as the function so you can use the functions and other properties without calling it or parameter name.
Example:
val phoneNumber = "8899665544"
phoneNumber.apply {
println(contains("8"))
println(length)
}
You can see in the above example the functions of String class directly invoked inside the lambda funtion.
takeIf
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null
Example:
val phoneNumber = "8899665544"
val number = phoneNumber.takeIf { it.matches("[0-9]+".toRegex()) }
In above example number will have a string of phoneNumber only it matches the regex. Otherwise, it will be null.
takeUnless
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? = if (!predicate(this)) this else null
It is the reverse of takeIf.
Example:
val phoneNumber = "8899665544"
val number = phoneNumber.takeUnless { it.matches("[0-9]+".toRegex()) }
number will have a string of phoneNumber only if not matches the regex. Otherwise, it will be null.
You can view similar answers which is usefull here difference between kotlin also, apply, let, use, takeIf and takeUnless in Kotlin
According to my experience, since such functions are inline syntactic sugar with no performance difference, you should always choose the one that requires writing the least amount of code in the lamda.
To do this, first determine whether you want the lambda to return its result (choose run/let) or the object itself (choose apply/also); then in most cases when the lambda is a single expression, choose the ones with the same block function type as that expression, because when it's a receiver expression, this can be omitted, when it's a parameter expression, it is shorter than this:
val a: Type = ...
fun Type.receiverFunction(...): ReturnType { ... }
a.run/*apply*/ { receiverFunction(...) } // shorter because "this" can be omitted
a.let/*also*/ { it.receiverFunction(...) } // longer
fun parameterFunction(parameter: Type, ...): ReturnType { ... }
a.run/*apply*/ { parameterFunction(this, ...) } // longer
a.let/*also*/ { parameterFunction(it, ...) } // shorter because "it" is shorter than "this"
However, when the lambda consists of a mix of them, it's up to you then to choose the one that fits better into the context or you feel more comfortable with.
Also, use the ones with parameter block function when deconstruction is needed:
val pair: Pair<TypeA, TypeB> = ...
pair.run/*apply*/ {
val (first, second) = this
...
} // longer
pair.let/*also*/ { (first, second) -> ... } // shorter
Here is a brief comparison among all these functions from JetBrains's official Kotlin course on Coursera Kotlin for Java Developers:
I must admit that the difference is not so obvious at first glance, among other things because these 5 functions are often interchangeable. Here is my understanding :
APPLY -> Initialize an object with theses properties and wait for the object
val paint = Paint().apply {
this.style = Paint.Style.FILL
this.color = Color.WHITE
}
LET -> Isolate a piece of code and wait for the result
val result = let {
val b = 3
val c = 2
b + c
}
or
val a = 1
val result = a.let {
val b = 3
val c = 2
it + b + c
}
or
val paint: Paint? = Paint()
paint?.let {
// here, paint is always NOT NULL
// paint is "Paint", not "Paint?"
}
ALSO -> Execute 2 operations at the same time and wait for the result
var a = 1
var b = 3
a = b.also { b = a }
WITH -> Do something with this variable/object and don't wait for a result (chaining NOT allowed )
with(canvas) {
this.draw(x)
this.draw(y)
}
RUN -> Do something with this variable/object and don't wait for a result (chaining allowed)
canvas.run {
this.draw(x)
this.draw(y)
}
or
canvas.run {this.draw(x)}.run {this.draw(x)}