Mapping components to existing code

Developers can customize the code generation process by providing a mapping between a UI Package and an existing code component instead of the generated code. This is beneficial when the existing implementation has features that cannot be achieved by the generated code such as animation or complex behavior (such as a drop down menu).

Developers specify how to map components using a mapping file. A mapping file tells the code generator, at minimum, how to reach the desired composable function so that the right client code can be created.

Mapped component overview diagram

Here is an example:

In Figma, a designer creates a Card component that contains an instance of a Play Bar component, packages both components, and sends them to a developer.

When the developer imports the UI packages from Figma, two directories are created in ui-packages: card and play_bar. When they build the project, two composable functions are created: Card and PlayBar. Typically, because Card contains a Play Bar instance in Figma, in code the Card composable function contains a call to the PlayBar composable.

However, the designer and developer want Card to instead use an existing composable, MyExistingPlaybar, which has functionality that is hard to describe in Figma. So the developer adds a mapping file called play_bar.map that maps the play_bar UI package to MyExistingPlaybar:

{
    "target": "MyExistingPlaybar",
    "package": "com.example.myApp"
}

Now, when the developer builds the project, Card calls MyExistingPlaybar instead of PlayBar. Note that MyExistingPlaybar must have the same parameters as PlayBar (although there can be a few differences, as described in Additional Directives below).

Mapping File

In your Android Studio projects, mapping files are added under ui-package-resources/mappings next to the ui-packages folder. Relay will look for your mapping files during build.

Mapping file in the project view

The name of a given mapping file must match the name of the UI Package folder for the component it replaces. So play_bar.map maps the UI Package in the ui-packages/play_bar folder to an existing code component.

Mapped Variants

If a Figma component has variants, then the generated composable will have enum parameters that encode the variant (as described in the Handling Design Variants tutorial). If you want to map a Figma component with variants to existing code, it needs to be mapped to a composable that takes the same parameters as the generated composable, except that the enum parameter is replaced by a string parameter called design. For example, suppose there is a Figma component called Chip with a variant whose property is ChipType. Chip’s generated composable signature looks like this:

@Composable
fun Chip(
    modifier: Modifier = Modifier,
    chipType: ChipType = ChipType.Red,
    chipText: String
) { ... }

If you want to have the Chip Figma component map to an existing MyChip composable, then the signature for MyChip must be specified as the following (assuming no additional directives are specified):

@Composable
fun MyChip
    modifier: Modifier = Modifier,
    design: String = "ChipType=Red",  // See note below
    chipText: String
) { ... }

Conceptually, this suggests that the existing code component is capable of the same design variants as the Figma component.

Additional Directives

Suppose the composable function we want to target as the following signature:

@Composable
fun MyChip(
    modifier: Modifier = Modifier,
    design: String = "ChipType=Red",
    description: String  // instead of chipText
) { ... }

We can add a fieldMappings block that affects how parameters are mapped. In this case, it contains a mapping from the chipText parameter in the Chip to the description parameter in MyChip.

{
    "target": "MyChip",
    "package": "com.example.myApp",
    "fieldMappings": [
        {
            "type": "parameter",
            "source": "chipText",
            "target": "description"
        }
    ]
}

The types for the fieldMappings block include:

  • type: parameter maps a UI package field to a code parameter.
    • source: Name of parameter as specified in the UI package.
    • target: Name of parameter as specified in the target code component.
  • type: lambda maps a UI package field to a content lambda.
    • source: Name of parameter as specified in the UI package.
    • target: Name of parameter as specified in the target code component.
  • type: modifier maps a UI package field to a modifier method. Note: This mapping type is being incubated, in particular the handling of receiver scopes.
    • source: Name of parameter as specified in the UI package.
    • method: Method on the Modifier object that should be invoked in generated code.
    • parameter: Name of parameter within the specified Modifier method.
    • library: The qualified package name to import to access the Modifier method.
    • scope: One of two values to indicate the scope of the Modifier:
    • any: The modifier can be used in any receiver scope.
    • relay: The modifier must be used in the receiver scope of Relay’s RelayContainer object.

See also