Map 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 target 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.json 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 looks for mapping files during build.

Mapping file in the project
view

Generate a mapping file

Relay can generate a mapping file for any imported UI Package. Follow these steps:

  1. Right-click on the package folder or any file inside the target ui-package folder. Select Generate mapping file.

    Generate mapping file
affordance

  2. Configure the following options in the dialog:

    Dialog for generating mapping
files

    • File location: Sets the location for the generated mapping file.

    • Target composable: Sets the custom composable that is used instead in place of the generated composable. You have the option of using an existing composable or creating a new one from the dialog. Creating a new composable creates a composable with the same parameters as defined in the UI package.

    • Generated file: Sets the generateImplementation and generatePreview options in the mapping file. See Mapping file contents below for more details.
  3. Click Generate mapping file. A new mapping file is created inside of ui-package-resources/mapping folder with the specified configurations.

You can also open the Generate mapping file dialog from the Relay package module UI using these steps:

  1. Click any file for a UI package inside the target ui-package folder.

  2. If the Relay tool window does not open automatically, click the Relay icon to open the window.

  3. Click the Generate mapping file button under Package Options.

    Generate mapping file
affordance

Mapping file name

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

Mapping file contents

The mapping file contains the following properties:

  • target: (Required) The name of your custom composable function. By default, this is the name of the function created by generated code.

    "target" : "CustomComposableName"
    
  • package: (Required) Name of the package that your custom composable sits in. By default, this is the package of the function created by generated code.

    "package" : "com.example.podcastapp.ui.components"
    
  • generateImplementation: (Optional) true or false. If true, an implementation of this UI Package is still created in the generated code file. If false, the implementation isn't created. By default, this is true.

    "generateImplementation" : true
    
  • generatePreviews: (Optional) true or false. If true, a preview of the mapped custom component is created in the generated code file. If false, no preview is created. By default, this is true.

    "generatePreviews" : true
    

Mapped variants

If a Figma component has variants, then the generated composable contains 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 must be mapped to a composable that takes the same parameters as the generated composable. For example, for 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 have the same signature as the generated composable (assuming no additional directives are specified). Conceptually, this suggests that the existing code component is capable of the same design variants as the Figma component.

Additional directives

For example, if the composable function you want to target has the following signature:

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

You can add a fieldMappings block to the mapping file 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:

  • 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.
  • 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.
  • modifier: Maps a UI Package field to a modifier method.

    • 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.