Hi, how can I modify the color of a Button when cl...
# compose
a
Hi, how can I modify the color of a Button when clicked ?
m
1.
remember
a
MutableState
of the color 2. Use the state's value to set the button color (see this example: https://developer.android.com/jetpack/compose/themes?hl=en#component-styles) 3. Modify the state's value in the
onClick
function of the button. Jetpack Compose takes care of the update propagation redrawing, etc.
a
oh I can modify values of the button itself in the
onClick
function ? how ?
m
You can't modify the button itself, you have to modify a
MutableState
that the button reads. That's what allows Jetpack Compose to "know" you modified something and "tell" the button that something changed (to recompose).
a
oh yeah I see, my button is actually a
Task
component right now, it's for a todo application where I have a
todo
MutableState value which contains
Todo
instances, and in my App I do a
todo.forEach { Task(it) }
but I guess that it's not the right way to do it ? It's my first Compose app (I literally started 30 minutes ago)
m
So you need to modify the
Todo
instances? In that case, you have to adopt some kind of bigger state management. Redux and the like are common patterns currently. (Shameless self-promotion, I'm developing a different state management solution that's based on functional programming if that's up your alley: https://github.com/magneticflux-/jetpack-compose-optics/tree/40142514e00a2fac94ecde802121148fcee6a522/example/src/main/kotlin/com/skaggsm/compose)
(I assume you derive the color of the button from the
Todo
object, like a validation state or something)
a
Yes okay I have
Copy code
data class Todo(val task: String) {
	var state = TodoState.TO_DO
}


enum class TodoState(val color: Color) {
	DONE(Color.Green),
	IN_PROGRESS(Color(150, 200, 220)),
	TO_DO(Color(200, 200, 200))
}
And my component is for now
Copy code
@Composable
fun Task(todo: Todo) {
	Button(
		colors = ButtonDefaults.buttonColors(todo.state.color),
		onClick = {
			todo.state = TodoState.DONE
		}
	) {
		Text(todo.task)
	}
}
How should I update there the color whenever the task changed its state ?
m
You have to implement
equals
and
hashcode
for your
Todo
class now since you put a mutable property in it and that's how Compose detects changes.
a
don't these methods are created when you make a data class ?
m
Nope! The automatic methods generated in
data
classes only include the constructor parameters. You should also read this caveat about using mutable objects: https://developer.android.com/jetpack/compose/state#use-other-types-of-state-in-jetpack-compose And about
data
classes: https://kotlinlang.org/docs/data-classes.html
The compiler automatically derives the following members from all properties declared in the primary constructor:
Ultimately, what you need to do is "hoist" the state in Compose terms. • You can do it the way it's described in the documentation here: https://developer.android.com/jetpack/compose/state#state-hoisting • You can do it the React/Redux way if you're more familiar with that • You can do it the functional programming way with my experimental library that I made because I was personally unhappy with the previous two techniques
a
I've never used Redux/React before (I don't even know what is Redux)
I'm reading the article and trying things
👍 1
t
Copy code
data class Todo(val task: String) {
	var state by mutableStateOf(TodoState.TO_DO)
}

@Composable
fun Task(todo: Todo) {
	Button(
		colors = ButtonDefaults.buttonColors(todo.state.color),
		onClick = {
			todo.state = TodoState.DONE
		}
	) {
		Text(todo.task)
	}
}
This might alreday be enough to get you started (notice the mutableStateOf in the data class). But as Mitchell mentioned, you should hoist your state, i.e. not have your composable mutate the state directly but have the caller do that. Compose is somewhat confusing with its state model at the beginning, but it get's better!
a
I read multiple times the hoisting part but I still don't get it sorry :c
z
Hoisting state just means when you have a composable that has some internal state (ie a remember mutableStateOf) and you refactor that state to instead be passed into the composable as a parameter. It means the state goes from being privately owned by the composable to owned by (and often controlled by) its parent/the composable calling it.
r
You have to implement equals and hashcode for your Todo class now since you put a mutable property in it and that's how Compose detects changes.
I don't understand the "that's how Compose detects changes" part of that. The code that Tobias has written works without implementing those methods, right?
a
Yes
m
@Rick Regan Compose detects changes (by default) by using the
equals
method on the previous and new objects. If they are the equal, a change is not dispatched. The missing
equals
method is only one issue with that code; the main issue is that the class is mutable, so Compose is unable to compare previous values (because they were mutated instead of replaced). For more info, see the caveat I mentioned above: https://developer.android.com/jetpack/compose/state#use-other-types-of-state-in-jetpack-compose Tobias's code uses a
MutableState
inside the data class, so it compares the
TodoState
enums, not the entire data class. The enums have
equals
implemented and are immutable, so they work correctly. This isn't recommended, however, because it couples your data types to Compose specifically and means that your data types are mutable, which is undesirable in more complex applications.
r
Thanks Mitchell for the explanation; it's helped me reflect on a few important issues (structural/referential equality and immutability). I have been using (hoisted) classes with a pattern like
Copy code
class MyState(data: MyData, ...) {
var data by mutableStateOf(data)
...
}
This triggers recomposition when the variable
data
(the reference) changes. I think what you're saying is that without an overriding
equals
and
hash
for
MyData
, there won't be a structural equality check, which would lead to unnecessary (but harmless if they're idempotent) recompositions if the objects are structurally equal. Also, I think I understand your point about immutability to a point, but it seems that since you have to put a
mutableStateOf()
at some level, you're going to have a mutable object.
680 Views