Turning Designs into Code with Relay: Google’s Figma to Compose Plugin

Luke Simpson
7 min readOct 6, 2022

--

Over the years there have been a number of attempts to automatically generate code from UI designs, namely Zeplin and its little xml layout snippets. With the introduction of the Compose UI toolkit it makes sense that someone would have another shot at this and, sure enough, during the 2021 Android DevFest one of the talks previewed an internal Google project that aims to convert designs into code or as they put it: “Turning [design] handoffs into high-fives.” 🙌

I responded to the expression of interest to participate in the early-access program but when nothing further was announced at Google I/O this year I assumed the project was discontinued. Then a few months ago I received an email to complete a survey about our use of both Compose and Figma and eventually received access to the alpha program.

The timing coincided with a recent overhaul of our design systems, our designers onboarding Figma, and the Android team setting up our Compose themes, styles and components. So we spent several innovation days working with this tool and — while it is really powerful and better than its predecessors — we ultimately decided it didn’t suit our current needs. Following is a step-by-step recount of the setup and workflow with tips and gotchas we discovered along the way, as well as justification for our overall assessment.

Step 1: In Android Studio, install plugin and dependencies

The setup in Android Studio is simple but the Gradle installation instructions omit the need for a pluginManagement block in settings.gradle; without this, Gradle won’t find the Design to Code plugin:

pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}

Step 2: In Figma, install plugin, setup access token and isolate components

The setup for Figma is initially straightforward: just download the plugin and setup an access token. However, I quickly discovered that our Figma components were not isolated in the way the plugin expected.

Our design system uses mega-components containing all variants for a component for both iOS and Android. This allows designers to build a screen for iOS and then simply toggle the components to the equivalent Android variant. The plugin also makes it necessary to house a single component in its own file as the link to the file is used in the import process.

For the purposes of this investigation I setup a new file containing only our primary button and its variants: default, pressed and disabled:

Step 3: Export component from Figma

In Figma we can now run the Design to Code plugin, where we enter a short description and have the option to view the parameters and handlers it plans to export. Under the hood these Figma-defined values help build a more-complete Composable for us to use in Android Studio:

  • Content parameters: Used to specify non-static data in the component, e.g. the button label. This step relies on having separate layers for each content parameter and was another aspect that didn’t work out-of-the box for us.
  • Interaction handlers: Whether the component should have tap, double tap and/or long press behaviours. These can be specified in the Figma design file and automatically picked up, or added at this export stage.

Once we’re happy with the export options we save the file to version history and simply copy the file link for import in Android Studio.

Step 4: Import “UI Component” to Android Studio

It was interesting to discover that we don’t actually import ready-to-use Composable widgets. Instead, we import a set of metadata — called a UI Component — that describes how to generate the Composables, as well as a reference to the original Figma design for future updates.

The import wizard opens with fields for both the Figma file link and the method with which the style attributes are imported and mapped to the existing Compose theme. I initially selected the Material 2 option and discovered a large number of warnings in the wizard.

First screen in the Android studio import wizard
Second screen in the Android studio import wizard

Each warning relates to a failure to map a design style from Figma to a corresponding Compose theme attribute. There’s little value in importing the literal values as we want to be able to make universal updates to these components in the future. Fortunately, there is a custom configuration option to achieve this mapping.

The custom configuration json can specify specific components and their fields, as well as styles, themes and colours. In our case I simply wanted to map Figma’s colour names, e.g. highlights/digitalBlue, to the primary colour defined in our Compose theme.

In its simplified form our custom config contains:

{
“figma”: {
“colors”: {
“highlights/digital-blue”: “myapp.sys.color.primary”
}
},
“compose”: {
“colors”: {
“myapp.sys.color.primary”: “MaterialTheme.colors.primary”
}
},
“options”: {
“packages”: {
“MaterialTheme”: “androidx.compose.material”
}
}
}

We then re-imported the component but this time pointed the plugin to our custom configuration file above. This resulted in fewer warnings in the wizard — so the mappings appear to work!

The resulting import in Android Studio contains several files including icons, preview images (empty placeholders), fonts (empty) and a long json file containing all of the attributes defined in Figma:

Step 5: Generate Composables

To do this we simply build the app and observe a new file in the build folder named PrimaryButtonAndroid. Note that we encountered a cryptic compilation issue here that we could only get around by adding a small error to our custom config json. 🤷‍♂

The generated file in fact contains several Composable functions that reflect the three button variants defined in Figma and the associated previews. To use a specific variant there is an enum that can be selected in the constructor, i.e.

PrimaryButtonAndroid(state = State.Disabled)

Below is the top portion of the generated file:

 enum class State {
Default, Pressed, Disabled
}

/**
* Primary button default/pressed/disabled
*
* This composable was generated from the UI package
* ‘primary_button_android’.
* Generated code; do not edit directly
*/
@Composable
fun PrimaryButtonAndroid(
modifier: Modifier = Modifier,
state: State = State.Default,
onPrimaryButtonAndroidTapped: () -> Unit = {}
) {

when (state) {
State.Default -> TopLevelStateDefault(
onPrimaryButtonAndroidTapped = onPrimaryButtonAndroidTapped,
modifier = modifier
) {

LabelStateDefault()
}

State.Pressed -> TopLevelStatePressed(
onPrimaryButtonAndroidTapped = onPrimaryButtonAndroidTapped,
modifier = modifier
) {

LabelStatePressed()
}

State.Disabled -> TopLevelStateDisabled(
onPrimaryButtonAndroidTapped = onPrimaryButtonAndroidTapped,
modifier = modifier
) {

LabelStateDisabled()
}
}

@Preview(widthDp = 328, heightDp = 48)
@Composable
private fun PrimaryButtonAndroidStateDefaultPreview() {
MaterialTheme {
Container {
PrimaryButtonAndroid(
onPrimaryButtonAndroidTapped = {},
state = State.Default,
modifier = Modifier.rowWeight(1.0f)
)
}
}
}
...

Pretty cool, but …

We couldn’t help but feel this tool was created by designers that are sick of developers not implementing their designs pixel-perfectly (fair enough!) and as a result left no room for flexibility. Rather than let us import base Composables that we can adjust as necessary, e.g. the attribute/style names, we are forced to overhaul the structure of our design system and designer workflows, and write comprehensive configuration mappers to achieve the desired result.

We also considered build times and the impact on productivity if these were allowed to increase drastically over time. We didn’t benchmark the impact on build times for the one component we generated but I imagine it would be non-negligible when dozens of components are generated each build.

I’m sure there is a niche for this technology and workflow, perhaps a small-ish team working on while-labelled apps, but for us we felt it created more complexity for little value compared with manually building the equivalent components.

And by taking care to setup our Compose themes and components correctly we are still able to make global changes manually, which is possibly more intuitive than re-importing the Figma file and mapping any new attributes.

The plugins are now publicly available at https://d2c.material.io/ if you’re interested in trying it out. I’d love to hear from others that have different experiences or know how we might be able to address some of the concerns mentioned above. Feel free to leave a comment or hit me up on Twitter: @luke_simmo.

Big thanks to our designer Danny for helping with Figma and nerd-ing out on this with me!

Edit: This plugin now has an official name: Relay. Follow these instructions to migrate to the new namespace if you were using this tool prior to the name change.

--

--