Why does mutableStateOf without remember work sometimes? - kotlin

I've been playing with Jetpack Compose Desktop. I noticed something I really don't understand:
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
#Composable
#Preview
fun App() {
var text by mutableStateOf("Hello, World!")
MaterialTheme {
TextField(text, onValueChange = { text = it })
Button(onClick = {
text = "Hello, Desktop!"
}) {
Text(text)
}
}
}
fun main() = application {
Window(onCloseRequest = ::exitApplication) {
App()
}
}
Why am I able to change the text in the TextField? I thought, that on every recompose the mutable state get reinstanciated with the initial Value: so the Text should not be able to change
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
#Composable
#Preview
fun App() {
var text by mutableStateOf("Hello, World!")
Column {
TextField(text, onValueChange = { text = it })
Button(onClick = {
text = "Hello, Desktop!"
}) {
Text(text)
}
}
}
fun main() = application {
Window(onCloseRequest = ::exitApplication) {
App()
}
}
However, if you replace the MaterialTheme with an Column it suddendly works as expected and you aren't able to change the text in the TextField.
Why is that? Is that a bug or a feature?

It's a feature of Compose about scoping and smart recomposition. You can check my detailed answer here.
What really makes your whole Composable to recompose is Column having inline keyword.
#Composable
inline fun Column(
modifier: Modifier = Modifier,
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
content: #Composable ColumnScope.() -> Unit
) {
val measurePolicy = columnMeasurePolicy(verticalArrangement, horizontalAlignment)
Layout(
content = { ColumnScopeInstance.content() },
measurePolicy = measurePolicy,
modifier = modifier
)
}
Let's say you have a Composable sets background color initially at composition and changes at each recomposition which creates its own scope. Lambda without inline is considered a scope.
#Composable
fun RandomColorColumn(content: #Composable () -> Unit) {
Column(
modifier = Modifier
.padding(4.dp)
.shadow(1.dp, shape = CutCornerShape(topEnd = 8.dp))
.background(getRandomColor())
.padding(4.dp)
) {
content()
}
}
With
#Composable
fun App2() {
var text by mutableStateOf("Hello, World!")
RandomColorColumn {
TextField(text, onValueChange = { text = it })
Button(onClick = {
text = "Hello, Desktop!"
}) {
Text(text)
}
}
}
And one with Column
#Composable
fun App() {
var text by mutableStateOf("Hello, World!")
Column(modifier = Modifier.background(getRandomColor())) {
TextField(text, onValueChange = { text = it })
Button(onClick = {
text = "Hello, Desktop!"
}) {
Text(text)
}
}
}
Random color function
fun getRandomColor() = Color(
red = Random.nextInt(256),
green = Random.nextInt(256),
blue = Random.nextInt(256),
alpha = 255
)
You will see that Column background color will change every time you change text but not with App2()

I thought, that on every recompose the mutable state get reinstanciated with the initial Value: so the Text should not be able to change
The point of having a state when recomposing is to prevent a variable from being re-initialized. The initial state is used only once when the first composition occurs and isn't used again when further recompositions occur. If however the composable is removed from the UI tree and then reinstated later on, the state of the variable will be destroyed and re-initialized as this is not really a recomposition but a composition (as if it were happening for the first time).

Related

Why won't my checkbox UI update on the first click, but will update on every click after that?

I have a Jetpack Compose for Desktop UI application that shows a popup with a list of enums, each of which has a checkbox to toggle them:
Sorry for the long code, this is as small a MCVE as I could make of it.
enum class MyEnum {
A, B, C
}
data class MyFilter(val enums: Collection<MyEnum>)
fun main() = application {
Window(onCloseRequest = ::exitApplication) {
App()
}
}
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun App() {
var filter by remember { mutableStateOf(MyFilter(emptyList())) }
MaterialTheme {
var show by remember { mutableStateOf(false) }
if (show) {
val selected = remember { filter.enums.toMutableStateList() }
AlertDialog({ show = false }, text = {
Column {
MyEnum.values().forEach { e ->
Row {
val isSelected by remember { derivedStateOf { e in selected } }
Checkbox(isSelected, { if (isSelected) selected.remove(e) else selected.add(e) })
Text(e.name)
}
}
}
}, buttons = {
Button({
filter = MyFilter(selected.toList())
show = false
}) { Text("OK") }
})
}
Button({ show = true }) { Text("Open") }
}
}
The problem here is, the very first time after opening the dialog, a click on a checkbox will properly update the underlying selected list, but it won't update the UI. So the checkbox doesn't appear to change state. Every click after that will properly update the UI, including the changed state for the previous click.
This happens reliably, every single time the dialog is opened, but only on the first checkbox click.
Why does this happen, and how can I fix it?

Kotlin Jetpack Compose how to pass any icon and its color from outside

How could i pass any icon and icon color from outside? In React Native i would use icon?: React.ReactNode as a prop, but how to do this in Kotlin? Is there a type that allows to pass icon from outside? The code that i have below does not work, but it represents the idea that i want to achieve.
#Composable
fun IconField(
label: String,
icon: Boolean? = false, //In React native i would do this like icon?: React.ReactNode
) {
Row() {
if(icon)
Icon(
contentDescription = "",
modifier = Modifier
.size(16.dp))
Text(
text = (label)
)
}
}
From outside would want to pass tint and icon from outsie so that i could easily change icons and colors
IconField(label = "Icon Field", icon = Icons.Default.Check, tint = Color.Black, icon = true )
For your particular case you can simply pass icon as nullable argument
#Composable
fun IconField(
label: String,
modifier: Modifier = Modifier,
leadingIcon: ImageVector? = null // You can change ImageVector with Painter or ImageBitmap
iconTint: Color = MaterialTheme.colors.Black
) {
Row(modifier = modifier) {
leadingIcon?.let {
Icon(
modifier = Modifier.size(16.dp),
imageVector = leadingIcon,
contentDescription = null,
tint = iconTint
)
} ?: Box(Modifier.size(16.dp)) // You can set dummy box for save Text positioning
Text(text = label)
}
}

​ [Kotlin/JS with React] Is there a way to use React Portals?

The function I suppose I should use is:
createPortal(
children: ReactNode?,
container: Element,
key: Key? = definedExternally,
)
What I tried is doing something like this:
A component (Modal.kt)
val Modal = FC<Props> {
p {
+"A model modal"
}
}
Then in the main App component:
val App = FC<Props> {
val modalContainer = document.getElementById("menu-root") ?: error("Couldn't find menu root container!")
createPortal(Modal.create(), modalContainer)
}
And in the main method:
fun main() {
val container = document.getElementById("root") ?: error("Couldn't find root container!")
createRoot(container).render(App.create())
}
This doesn't work however. I've tried writing something akin do what you'd write in vanilla React:
In Modal.kt
val modalContainer = document.getElementById("modal-root") ?: error("Couldn't find modal root!")
val modalComponent = FC<Props> {
p {
+"A model modal"
}
}
val Modal = createPortal(
modalComponent.create(),
modalContainer
)
And in the main App component:
val App = FC<Props> {
+Modal // Is of type ReactPortal, so you can't invoke it like a function
}
This doesn't work, however, so I feel pretty stuck. Would anybody be able to provide insight into this? It would be greatly appreciated!

Angular 5 - Event emitter (Property 'update' does not exist on type ....)

I've got a component that I want to update when a person's name changes by emitting an event. My problem is the code doesn't compile because of an error. This is my code
ApplicationFormComponent
#Output() nameChange = new EventEmitter();
closeAccordion(isComplete: string, accordionToClose: string, accordion: NgbAccordion) {
if (accordionToClose === 'personal-details-panel') {
this.applicationStatusFlags.personalDetailsStatus = (isComplete === 'true');
this.nameChange.emit({ personId: this.personId });
}
}
ApplicationFormComponent.html
<name-display
[personId]="personId"
[placeHolderText]="'Hello'"
(nameChange)="update($event)">
</name-display>
NameDisplayComponent
import { Component, Input, OnChanges, SimpleChanges } from '#angular/core';
import { PersonService } from "../../../service/person.service";
#Component({
selector: 'name-display',
templateUrl: './NameDisplay.component.html',
providers: [PersonService]
})
export class NameDisplayComponent implements OnChanges {
constructor(private readonly personService: PersonService) { }
#Input() personId;
#Input() placeHolderText: string = "";
forename: string = "";
ngOnChanges(changes: SimpleChanges): void {
if (changes["personId"]) {
this.personService.getPersonDetails(this.personId).subscribe((res: IPersonDetails) => {
this.forename = res.forenames;
});
}
};
update(personId: number) {
alert("update name");
this.personService.getPersonDetails(personId).subscribe((res: IPersonDetails) => {
this.forename = res.forenames;
});
}
}
My problem is basically when I use angular cli with the command ng server --aot, it doesn't compile because of this error:
ERROR in src\app\component\ApplicationForm\ApplicationForm.component.html(42,9): : Property 'update' does not exist on type 'ApplicationFormComponent'.
I've written a similar component that uses an event emitter which doesn't have this problem, so I'm stuck with how to fix the error.
Any ideas?
It is because you are passing $event to method.
(nameChange)="update($event)"
But it accepts number.
update(personId: number) {
alert("update name");
}
Please change the method as below.
update(event:any) {
const personId = event as number
alert("update name");
}

mithril requires 'mithril' to be imported

I am writing a simple Mithril component, given below is the code
var m = require("mithril")
var MyComponent = {
view() {
return <button>Hello world!</button>;
}
};
export default MyComponent;
My problem is that in this scenario I am not using m as required, however when I remove this the app does not run, I get the following error
Uncaught ReferenceError: m is not defined(…)
It complains when you remove the m = require("mithril") line because when the JSX is transformed it becomes invocations of m().
var m = require("mithril")
var MyComponent = {
view() {
return <button>Hello world!</button>;
}
};
export default MyComponent;
becomes
var m = require("mithril");
var MyComponent = {
view: function view() {
return m(
"button",
null,
"Hello world!"
);
}
};
exports.default = MyComponent;
You can see the JSX transform live on the babel REPL