Open miguel-galarza opened 3 months ago
Thank you for the detailed request. I've assigned this to @iamralpht so that we can triage this soon.
Are you trying to change the button variant at runtime? If not, you don't need to use @DesignVariant
. Then you can name the instances differently and use those node names in your @Design
annotations.
Hi @rylin8, Yes, it is needed to change the button style at runtime, and there may be different states for different buttons in the same screen.
One thing you could do is create placeholder nodes in your main frame with different names like #button1
, #button2
etc. Then put @Composable (ComponentReplacementContext) -> Unit
customizations on those nodes and replace them with a @Composable
generated from a @DesignComponent
function for that button. For an example see https://github.com/google/automotive-design-compose/blob/05569c2d087855e5be7b883be330bbe9c92144be/integration-tests/validation/src/main/java/com/android/designcompose/testapp/validation/examples/VariantPropertiesTest.kt#L101
Hi @rylin8,
I have tested the proposed solution by also adding a text and a TapCallback to the replacement composable. In the example provided in VariantPropertiesTest.kt, it would be something like this:
@DesignComponent(node = "#SquareBorder") fun Square( @DesignVariant(property = "BorderType") type: SquareBorder, @DesignVariant(property = "#SquareColor") color: SquareColor, @DesignVariant(property = "#SquareShadow") shadow: Shadow, @Design(node = "#SquareBorder") onTapCallback: TapCallback, @Design(node = "#text") textButton: String )
Independent buttons are now working as expected taking the states "Enabled" and "Disabled", but if we add some prototype change from Figma itself, for example a "While Pressing" then if two buttons have the same state both will be changed during the "While pressing".
Is there some solution for this?
Thank you and BR,
I'm not sure why that wouldn't be working. We have a test that does something very similar to what you're describing. Please check this example and see if it is similar to your scenario: https://github.com/google/automotive-design-compose/blob/main/integration-tests/validation/src/main/java/com/android/designcompose/testapp/validation/examples/VariantInteractionsTest.kt
@miguel-galarza Can you share your example (or a reduction of your example) that reproduces this issue, because we think we have a test case that covers it (but must have a variation that we're not covering)?
Hi,
I have repeated the tests with version "0.30.0-rc01".
The use case would be to have some generic button that can used multiple times in a screen by setting individual text, and callback events. The button should contain the states: enabled, disabled and pressed while keeping the individual text associated, and callback to each button. I have included a screenshot of the figma document and a MainActivity.kt with the summary of the example. The generic button is called "#button1" and three instances of the button are added to the screen, two of them replaced with ComponentReplacementContext and the third not.
In the example the button states works individually, but the "whilePressing" event defined the Figma prototype does not work.
MainActivity.kt
package com.example.designcomposenavtest
import android.content.ComponentName
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.absoluteOffset
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.android.designcompose.ComponentReplacementContext
import com.android.designcompose.DesignSettings
import com.android.designcompose.TapCallback
import com.android.designcompose.annotation.Design
import com.android.designcompose.annotation.DesignComponent
import com.android.designcompose.annotation.DesignDoc
import com.android.designcompose.annotation.DesignVariant
import timber.log.Timber
enum class ButtonState {
disabled,
enabled
}
@DesignDoc(id = "id_document")
interface StageFrame {
@DesignComponent(node = "#stage1", isRoot = true)
fun Stage1(
@Design(node = "#button1-stage1") button1: @Composable (ComponentReplacementContext) -> Unit,
@Design(node = "#button2-stage1") button2: @Composable (ComponentReplacementContext) -> Unit,
)
@DesignComponent(node = "#button1")
fun ButtonVariants(
@DesignVariant(property = "#statebutton") buttonState: ButtonState,
@Design(node = "#textButton") textButton: String,
@Design(node = "#button1") onTapCallback: TapCallback
)
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setFigmaToken()
DesignSettings.enableLiveUpdates(this)
setContent {
App()
}
}
@Composable
fun App() {
//Button 1 States
var buttonState1: ButtonState by remember { mutableStateOf(ButtonState.disabled) }
var buttonText1: String by remember { mutableStateOf("Enabled b1") }
//Button 2 States
var buttonState2: ButtonState by remember { mutableStateOf(ButtonState.disabled) }
var buttonText2: String by remember { mutableStateOf("Enabled b2") }
StageFrameDoc.Stage1(
button1 = {
StageFrameDoc.ButtonVariants(
buttonState = buttonState1,
textButton = buttonText1,
onTapCallback = {
if (buttonState1 == ButtonState.enabled) {
buttonState1 = ButtonState.disabled
buttonText1 = "Disabled b1"
} else {
buttonState1 = ButtonState.enabled
buttonText1 = "Enabled b1"
}
}
)
},
button2 = {
StageFrameDoc.ButtonVariants(
buttonState = buttonState2,
textButton = buttonText2,
onTapCallback = {
if (buttonState2 == ButtonState.enabled) {
buttonState2 = ButtonState.disabled
buttonText2 = "Disabled b2"
} else {
buttonState2 = ButtonState.enabled
buttonText2 = "Enabled b2"
}
}
)
}
)
//Indicators of current status
Text(
modifier = Modifier.absoluteOffset(50.dp, 300.dp),
text = "btn 1: $buttonText1",
fontSize = 30.sp
)
Text(
modifier = Modifier.absoluteOffset(50.dp, 400.dp),
text = "btn 2: $buttonText2",
fontSize = 30.sp
)
}
private fun setFigmaToken() {
val figmaAccessToken = "123"
if (figmaAccessToken.isNotEmpty()) {
val intent = Intent().apply {
component =
ComponentName(
"com.example.designcomposenavtest",
"com.android.designcompose.ApiKeyService"
)
action = "setApiKey"
putExtra("ApiKey", figmaAccessToken)
}
startService(intent)
} else {
Timber.tag("MainActivity")
.e("FIGMA_ACCESS_TOKEN is not defined. Skipping setting Figma token.")
}
}
}
With the purpose of reusing Figma components, it is usually created a generic component in Figma with different variants. For example a #buttonGeneric with two variants (Enabled, Disabled) associated to a property #buttonState. From Figma, then different instances of the #buttonGeneric could be added in a screen and for each one it is possible to select a specific variant by modifying the property #buttonState. The image below, describes the use case:
The problem is that this approach of implementation is not working properly in design compose. When @DesignVariant is used, it is not possible to associate each specific instance to different buttons in the screen (nodes). Moreover when the variant is applied, the parameters of the node are lost:
It seems that when using DesignVariant a complete replacement of the instance by the generic one occurs.
The following issue Cannot change variant of a component on the stage · Issue #691 · google/automotive-design-compose (github.com) reports a similar perspective of the issue but this workaround add additional complexity to the code and does not solve the TapCallback issue. The approach also loses the simplicity of adding simple nodes.
It would be really helpful to apply specific variants to specific nodes without the need of creating additional components for each case.