Only this pageAll pages
Powered by GitBook
1 of 6

Flash Client Sdk

Development

Loading...

Loading...

Loading...

Loading...

Loading...

Understanding Configurability

Introduction

Before diving into how Server-Driven UI frameworks define components and their DSL (Domain-Specific Language), it's important to understand the concept of configurability.

In traditional app development, developers make components reusable by exposing props, styles, and behaviors allowing the same component to be used in different contexts with different configurations.

Flash builds on this same principle. The component constructs and DSL used in Flash are a direct extension of how we already think about reusable components in code. Instead of defining configurations in code, we now define them as structured data (typically JSON) that can be interpreted by the app at runtime.

By understanding configurability, you’ll better appreciate how server-driven components are designed, how much flexibility they offer, and how product teams can control UI behavior without additional development effort.

In the following sections, we’ll explore configurability through simple examples. We’ll start by creating a basic UI component, gradually make it configurable, and then use that foundation to define the Flash component construct and ultimately the DSL that powers it.

Component in React Native

React Native components require three fundamental properties to render:

  1. Styles - Defines the appearance of the component.

  2. Data - Provides content or values for the component.

  3. State - Manages dynamic changes in the component.

This document explores how to define a Domain-Specific Language (DSL) for Server-Driven UI (Flash), the reasoning behind its structure, and how to build a renderer capable of dynamically interpreting and rendering components.

But before we dive into Flash, let’s step back and look at how we build configurable components in our codebase today. We’ll start by implementing a basic SimpleComponent that renders static text, and then gradually enhance it to support configurability.

import { View, Text } from 'react-native';
import React from 'react';

const SimpleComponent = () => {
  return (
    <View style={{ backgroundColor: '#fff' }}>
      <Text style={{ color: '#000' }}>Hello World</Text>
    </View>
  );
};

This component always renders "Hello World", and styles are hardcoded.

Making the Component Reusable

To improve reusability, we pass styles and data as props:

const SimpleComponent = (viewStyle: ViewStyle, textStyle: TextStyle, data: string) => {
  return (
    <View style={viewStyle}>
      <Text style={textStyle}>{data}</Text>
    </View>
  );
};

Now, the component can render any text with configurable styles.

Supporting dynamic child components

The above component is reusable and supports configurable styles and data. But we can take it a step further by making the component’s children dynamic, allowing even greater flexibility and composition.

To allow rendering child elements dynamically:

const SimpleComponent = (style: ViewStyle, children: JSX.Element[], data: string) => {
  return <View style={style}>{children.map((child) => child)}</View>;
};

Example Usage:

const children = [
  <Text style={{ color: 'black' }}>Hello</Text>,
  <Text style={{ color: 'yellow' }}>World</Text>,
  <Image source={{ uri: 'https://some-logo' }} />,
  <View style={{ backgroundColor: 'red' }} />,
];

<SimpleComponent style={{}} children={children} data={} />

The `SimpleComponent` is now reusable and supports configurable styles, data and children.

In other words, we can also say that for a component to be configurable it should supports configurations for styles, children and data.

Such configurable component should take following three parameters as input:

  • styles: Applied to the root component.

  • children: Elements to be rendered inside the component.

  • data: Dynamic values for the component.


Supporting Server-Driven Configuration

Earlier, we discussed how to make components configurable in the codebase. During that process, we established a fundamental principle: configurable components should accept styles, children, and data as inputs.

When these configurations are supplied by a server, we refer to the component as server-driven. Flash compliant components must adhere to this construct and take styles, children and data as inputs.

To support server-driven configurations, the app must fetch these inputs from a remote source. For the app to correctly interpret and render these configurations at runtime, they must adhere to a predefined contract between the server and the mobile client. This contract is known as the DSL (Domain-Specific Language).

On the client side, the mobile app requires an interpreter and renderer that can understand this DSL and dynamically render the appropriate components on the device.

DSL

DSL is JSON representation of the component construct. It contains key and values which are necessary to define server driven components. Below is the basic version of Flash DSL.

{
  "name": "ComponentName",
  "styles": {},
  "components": [],
  "data": {}
}
  • name: React Native component name (e.g., View, Text, Image) or a custom component.

  • styles: Styling properties for the component.

  • data: Dynamic content for the component.

  • components: Child components, following the same structure recursively.


Handling Complex Layouts with Inflaters

Real-world mobile apps often involve complex layouts such as Tabs, Bottom Tabs, and Lists. In React Native, both simple elements like Text and complex structures like Tabs are treated as components. What makes components like Tabs special is that they understand how to render their children—for example, Tabs will always lay out its children in a tabular format.

Similarly, Flash handles complex layouts using the concept of special components that know how to interpret and render their children. In Flash, these special components are called Inflaters.

As Inflaters are also components, they follow the same server driven component construct.

A DSL representation for a TabLayout might look like this:

TabLayoutInflater is a component which renders components given input to it in Tabular format.

{
  "name" : "TabLayoutInflater",
  "styles" : {},
  "components" : [],
  "data" : {}
}

By introducing Inflaters, we enable the DSL to define not just how components look, but how they behave making complex layouts configurable without changing app code.


Business Components

The Flash DSL and component construct allow developers to configure a component’s styles, hierarchy, and data. With the help of Inflaters, even complex layouts can be defined in a server-driven manner.

However, in real-world mobile apps, components often encapsulate business logic, state management, and user interactions. The current Flash DSL does not support configuring business logic directly. But what if these logic-heavy components also need to be configurable in terms of styles, data, and children?

The Flash framework is designed with this in mind. Components that include business logic but still require configurability are supported using the same Flash component construct. We refer to these as Business Components or Pre-Baked Components.

Let’s understand this with an example.

Consider an CardComponent, which includes business logic such as fetching data via an API and updating internal state on a button click. It also contains a Button and a TabNavigator as children.

const CardComponent = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch("https://api.xyz.com/card").then((data) =>
      setData(data)
    );
  }, []);

  const onPress = useCallback(() => {
    if (data.sectionName.contains("some-string")) {
      navigation.push("route");
    }
  }, [data]);

  return (
    <View style={{ backgroundColor: "#000" }}>
      <Pressable onPress={onPress}>
        <Text> ${data.sectionName} </Text>
      </Pressable>
      <Tab.Navigator>
        <Tab.Screen
          name="First"
          component={<FirstTabComponent data={data.firstTab} />}
        />
        <Tab.Screen
          name="Second"
          component={<SecondTabComponent data={data.secondTab} />}
        />
      </Tab.Navigator>
    </View>
  );
};

const FirstTabComponent = (data) => {}

const SecondTabComponent = (data) => {}

While the Flash DSL doesn't (yet) support configuring the business logic itself, it does support configuring the JSX structure of the CardComponent. To make this possible, the component must follow the Flash component construct—that is, it should accept styles, children, and data as input.

Lets make CardComponent Flash compliant. It can be done in following steps

  • Update the method signature to follow the flash component construct

  • Use TabLayoutInflater in-place of Tab.Navigator

  • Pass the styles, data and components to TabLayoutInflater

Here's the updated CardComponent:

type ConfigurableProps = {
  style: ViewStyle;
  components: JSX.Element[];
  data?: any;
};

const CardComponent = ({ style, components }: ConfigurableProps) => {
  const [cardData, setUpCardData] = useState(null);

  useEffect(() => {
    fetch("https://api.xyz.com/card").then((result) =>
      setUpCardData(result)
    );
  }, []);

  const onPress = useCallback(() => {
    if (cardData?.sectionName?.includes("some-string")) {
      navigation.push("route");
    }
  }, [cardData]);

  return (
    <View style={{ backgroundColor: "#000" }}>
      <Pressable onPress={onPress}>
        <Text> {cardData?.sectionName} </Text>
      </Pressable>
      <TabLayoutInflater
        styles={style}
        data={cardData}
        components={components}
      />
    </View>
  );
};

const TabLayoutInflater = (props: ConfigurableProps) => {
  const [tabs, setTabs] = useState<Array<Component>>();

  useEffect(() => {
    const c = props.components.map((child) => {
      const Component = getComponent(child.name);
      return (
        <Component
          style={child.styles}
          components={child.components}
          data={child.data}
        />
      );
    });

    setTabs(c);
  }, []);

  return (
    <Tab.Navigator>
      {tabs.map((tab, index) => (
        <Tab.Screen key={index}>{tab}</Tab.Screen>
      ))}
    </Tab.Navigator>
  );
};

const FirstTabComponent = (props: ConfigurableProps) => {};
const SecondTabComponent = (props: ConfigurableProps) => {};

The CardComponent can now have capabilties

  • Re-order Tabs FirstTabComponent, SecondTabComponent

  • Add new Tab

  • Change Title of Tab

  • Change styles of Tab

Configurability in Flash

Flash is designed to offer maximum flexibility to developers. It allows them to choose the level of configurability that best fits their needs. Developers can make an entire screen configurable, or just a specific section of the screen. They also have full control over how deeply nested the configurability should go whether it’s at the screen level, component level, or within individual child components.

Flash Client

Overview

Server-Driven UI (SDUI) is a technique where the server defines the structure, style, and behavior of the user interface. This enables a highly dynamic and adaptable UI that can be updated without requiring a new app release. Flash Client, a Server-Driven UI (SDUI) framework for React Native, allows developers to leverage server-defined configurations to render dynamic components, layouts, and behaviors directly in the app.

In this section, we’ll explore how to make specific parts of the UI compatible with SDUI, using Flash Client SDK to allow for greater flexibility and remote configuration.

Why SDUI

In addition to layout flexibility, SDUI empowers you to control user behavior through configuration—enabling dynamic workflows, feature toggles, and contextual experiences. This approach opens up opportunities for experimentation, reduces the need for frequent app releases, and accelerates the overall development cycle.

Consider a scenario such as a ticket booking platform where the content changes frequently, but the placement of UI elements and the overall visual structure remains consistent. SDUI is valuable in this case: rather than hardcoding layouts, configuration related to the screen is delivered over the air, allowing the server to define the screen’s layout dynamically.

SDUI also enables:

  • Real-time UI updates.

  • Customizable UI components.

  • Reduced app release frequency.

  • Rapid experimentation with UI changes.

Flash Client integrates this SDUI paradigm to ensure flexibility in app development. Read more about the capabilities of Flash Client SDK for rendering dynamic components and layouts.

UI elements

A UI element in Flash Client refers to any component used to define the layout and design of a screen. Examples include View, Text, Button, and Image, which serve as the fundamental building blocks of the user interface. These elements can be customized, styled, and updated dynamically based on the configurations provided by the server.

In Flash Client, these UI elements are described in JSON format, enabling developers to dynamically alter layouts and styles at runtime through backend configurations.

Behaviour

Behavior defines the dynamic aspects of a UI element, such as events (e.g., taps, scrolls, or input changes) and the corresponding actions that should be triggered in response. Flash Client enables the definition of behavior in a highly configurable manner, linking events to actions such as navigating to a new screen, opening deep links, or displaying alerts.

Events and Actions will soon be a part of the Flash Client SDK. This feature will allow the server to specify actions that should occur when a specific event happens within the UI. For example:

  • Event: A button is clicked.

    • Action: Navigate to a new screen.

  • Event: A list item is tapped.

    • Action: Open a deep link or trigger a custom behavior.

This capability will enable developers to configure and control user interactions entirely from the server, providing flexibility in creating dynamic and context-sensitive user experiences.

Note: The full Event and Action functionality is scheduled for release soon, and will be integrated into future SDK versions, enabling the app to dynamically respond to user inputs based on remote configurations.

How it Works

The Flash Client SDK does not directly call the app or the API to fetch configuration data. Instead, the app provides the necessary data (such as layout configuration, UI components, styles, behaviors, etc.) to the SDK from an external source (like an API or local configuration storage). The SDK is then responsible for rendering the UI and managing the dynamic behavior based on the provided configuration.

Process Flow:

  1. App Fetches Configuration: The app makes the API calls to fetch the required configuration (e.g., layout, style, event-action mappings) from an external source. The Flash Client SDK relies on the app to provide this data.

  2. Flash Client SDK Receives Configuration: The app passes the configuration data to the Flash Client SDK, which interprets and renders the UI components dynamically.

  3. Rendering Components: Once the configuration data is provided to the SDK, the Flash Client SDK uses FlashComponent and Inflaters to render UI components dynamically according to the provided configuration.

  4. Event and Action Handling: The SDK uses the provided configuration to manage events and corresponding actions (e.g., button clicks, taps), ensuring that the UI responds dynamically to user interactions.

The Flash Client SDK itself does not initiate the fetching of configuration data but relies entirely on the app to supply the necessary data for rendering the UI and defining behaviors.

Flash Client SDK Overview

The Flash Client SDK is a powerful tool designed to manage configuration delivery and provide contextual configurations suited to the runtime environment. It is not responsible for fetching the data itself but relies entirely on the app to supply the necessary configuration. The SDK includes:

  • Component Registration: Register custom components for dynamic rendering.

  • Dynamic Layout & Behavior Rendering: Use provided configurations to manage layouts and behaviors.

  • Helper Utilities: Ensure easier integration with the app.

  • Type-Safe Interfaces: Guarantee proper usage of configuration data.

  • Validation Mechanisms: Ensure that the provided configuration data is correct.

The SDK’s core functionality revolves around rendering dynamic layouts (via FlashComponent and Inflaters) and managing dynamic behavior (via events and actions). However, for all of this to work, the app must provide the necessary configuration data at runtime.

Flow

Getting Started

Configurability refers to the ability of UI components and behaviors to be dynamically driven by server-provided configuration. In the context of Flash, configurability allows components to adapt their data, styles, and interactions based on JSON or similar config formats without requiring code changes or app updates. This includes making visual elements configurable through styling, functional behavior through events and actions, and structural flexibility through overrides.

By enabling configurability, developers can create flexible, testable, and version-aware UI experiences that are controlled remotely from the server.

UI

Behaviour (Coming Soon)

Behaviour defines how UI components respond to user interactions. It consists of:

  • Events: Triggers such as onPress, onChange, or onScroll

  • Actions: The side effects or logic executed in response, such as navigation, API calls, or analytics

By making behavior configurable, Flash enables dynamic user flows, feature toggles, and personalized interactions—without hardcoding logic into the app.

Steps to make UI Configurable

1

Make Component Configurable

Wrap your existing UI component in a way that allows it to consume configuration from the Flash system. This includes:

  • Accepting configProps for data and styles

  • Applying the received styles to the component’s layout

  • Rendering dynamic values based on the config-driven data

This wrapper ensures the component can adapt its appearance and behavior without code changes.

2

Register Configured Component

Once the component is made configurable, register it with the Flash system using a unique componentName. This makes it available for use in templates created via the dashboard.

Registered components must follow the expected schema and can now be referenced and configured dynamically at runtime by the server.

Steps to make Behaviour Configurable (Coming soon)

Configure Events

Events define how a component responds to user interactions (e.g., taps, changes, scrolls). In Flash, events are made configurable inside the component, and their configuration is tightly coupled with the component’s wrapper. Events are not configured separately in templates, they are defined and registered as part of making the component configurable.

If a component doesn’t use any events, this step can be skipped.

1

Identify Events in Component

Determine which interactions within the component should trigger behaviour such as onPress, onChange, or onScroll. These are considered the events for the component.

Not all components will have events, and that’s completely valid.

2

Make Event Configurable

Inside the component wrapper, wire up the event handler to read from configProps.events.

Ensure the event follows the standard schema defined for that event type so that the Flash engine can interpret and execute it consistently across different components.

This enables you to pass arguments, trigger actions, and compose conditional flows all from config.

3

Register Event

Register each event used by the component using a unique name, so the Flash system can map and handle it correctly.

If an event with the same name is already registered globally (with the same schema), re-registration is not needed.

Multiple events can be registered if the component supports more than one interaction.

Configure Actions

Actions define the side effects or operations that occur in response to events such as navigation, API calls, logging, or custom logic. In the Flash system, actions are configured and registered independently of components and events.

1

Make Actions Configurable

Design your action to accept arguments and callbacks from config and execute the desired logic. Ensure the action supports:

  • Arguments from config

  • Optional success and failure actions

  • Nested execution when needed

This structure allows actions to be chained or wrapped for retry/fallback logic.

2

Register Configured Actions

Register each action using a unique identifier via the Flash action registry. This makes the action available across all components and templates.

Since actions are decoupled from components, they can be reused across the app. The SDK uses the registered action name in the config to resolve and execute the correct logic when triggered by an event.

UI defines the visual structure and presentation of the screen. It includes such as buttons, cards, inputs, images, and layout containers. These components are configured via server-supplied data and styling properties, enabling the app to render screens dynamically while maintaining platform-specific theming and consistency.

components

Components

Components are the fundamental building blocks of the UI in Flash. They represent reusable visual elements such as Button, Text, Image, Card, etc., which are used to construct layouts and interactive screens.

To use components in Flash, they must go through specific steps: making them configurable (Flash compliant) and then registering them. This process ensures the components can respond to server-driven configuration for styling, data, and behaviour.

Component vs Confiured Component

A component refers to a standard React (or React Native) component that is static in nature it uses hardcoded props and styles and behaves the same way every time it is rendered.

A configured component is a wrapper around such a component that makes it Flash compatible It can:

  • Dynamically apply styles from config

  • Render data passed from config

  • Attach configurable events and actions

This transformation allows the component to be controlled remotely through the server.

Example:

Example
// Regular Non-Flash Compatible Component
const FooterButton = ({ text, onPress }) => (
  <Pressable onPress={onPress}>
    <Text>{text}</Text>
  </Pressable>
)

// Flash-Compatible Configured Component
import { FlashPressable, FlashText } from 'flash-client'

const FooterButtonFlash = (props: ConfigurableProps) => {
  const { data } = props
  const testId = data?.testId

  return (
    <FlashPressable
      nativeID={`${testId}-button-container`}
      onPress={data?.onPress}
      configProps={props}
    >
      <FlashText
        nativeID={`${testId}-button-title`}
        configProps={props}
      >
        {data?.text}
      </FlashText>
    </FlashPressable>
  )

Configured Component vs Registered Component

A configured component is capable of consuming server driven configuration, but it is not yet known to the Flash system until it is explicitly registered.

A registered component is a configured component that has been linked to a unique componentName using the registerComponent() method. Once registered, it becomes available for use in templates created on the Flash dashboard.

In short:

  • A configured component can read and apply config

  • A registered component is a configured component that the Flash system can recognize and render dynamically

Example
// Register Flash-Compatible Component to SDK
import { Flash } from 'flash-client'
import { FooterButtonFlash, CardComponent } from './components'

Flash.registerComponent({
  FooterButtonFlash,
  CardComponent
}

Component Type

To create a Flash template in realtime from backend, we need basic building blocks. It is not always possible to create complex templates with only building blocks which has complex ui or stateful logic. So, along with basic building blocks, we also need project specific prebaked components whose ui and logic is written in codebase. Similar to basic building blocks, we register prebaked components and make them available in the dashboard during template creation in realtime.

Type
Description
Example

BaseComponent

Stateless components with no children.

Text, Image, Toast

BaseComponentWithChildren

Stateless, layout-like components that support children.

View, Pressable

PreBakedComponent

Project-specific, complex components with custom logic, no children.

CardComponent, TeamCard

PreBakedComponentWithChildren

Complex project components that also support nesting/children.

Accordion, TabLayout, Modal

These types allow balancing flexibility (basic blocks) with performance and logic encapsulation (prebaked).

Flash Component Schema

Every UI element that is rendered in your app via Flash is described using a JSON object. This schema defines the structure, style, behavior, and children of a component — all of which are interpreted by the Flash renderer on the client side.


Component — The Core Unit

Component = {
  name: string;
  components?: Array<Component>; // Nested children
  styles?: Style;
  overrides?: Overrides;
  data?: PropData;
  dataId?: string;
}

Let’s break down each field:

Field
Type
Purpose

name

string

This must match the registered name of a component (via registerComponent). Example: "FooterButtonFlash"

components

Component[]

Nested or child components, used by layout-type components like View, ScrollView, etc.

styles

Style

Styling config like margin, padding, color, etc., applied to the root of the component.

overrides

Overrides

Style or prop overrides targeted via nativeID inside the component.

data

PropData

Dynamic props to control content and behavior (text, image source, backend data etc.).

dataId

string (optional)

Can be used for fetching external data via binding systems.


Example Component JSON

{
  "name": "FooterButtonFlash",
  "data": {
    "text": "Submit",
    "testId": "footer",
  },
  "styles": {
    "padding": 12,
    "backgroundColor": "#FF5733"
  },
  "overrides": {
    "footer-button-title": {
      "styles": {
        "color": "#FFFFFF",
        "fontSize": 16
      }
    }
  }
}

This will:

  • Render the component registered as FooterButtonFlash

  • Pass data.text to it

  • Style the outer wrapper with padding and background color

  • Override styles for inner FlashText using nativeID="footer-button-title"


Supporting Types

1. Style

Style = {
  [key: string]: ViewStyle | TextStyle | ImageStyle;
}
  • This is a union of style objects similar to React Native's StyleSheet.

  • It supports keys like:

    • margin, padding, flexDirection → from ViewStyle

    • color, fontSize, fontWeight → from TextStyle

    • resizeMode, tintColor → from ImageStyle

These styles are applied to the root of the component, unless used in overrides.


2. PropData

PropData = {
  [key: string]: string | number | boolean | null | PropData | Array<PropData>;
}
  • This is the data prop passed to the component.

  • It's fully dynamic and recursive — it can contain primitives, objects, or arrays.

  • Used to drive:

    • Text or image content

    • Test IDs, flags, booleans, etc.

Props inside data are typically used inside the component like:

const text = props.data?.text

3. Overrides

Overrides = {
  [nativeId: string]: {
    props?: PropData;
    styles?: Style;
  };
}

Overrides allow the server to reach deep inside a component and override:

  • Its props

  • Its styles

This is possible only if that inner component uses nativeID.

nativeID is a identifier that says: “You can target this element with overrides.”

Example Use Case

Imagine a card component that has a title and a subtitle. You want to override the color of the subtitle only in one use case without changing the entire card component.

You can assign nativeID="card-subtitle" and override it like:

"overrides": {
  "card-subtitle": {
    "styles": {
      "color": "#999999"
    }
  }
}

⚠️ If nativeID is missing, overrides will have no effect.

Configurable Props

To make the component configurable, the component should take configProps. configProps is passed as props to the component and has flash data.

/**
 * Represents configurable properties that can be passed to a component.
 *
 * - `components`: An optional array of child components. This allows for the nesting of components, enabling complex UI structures.
 * - `styles`: An optional object that defines the styling for the component. This allows the component to be visually customized.
 * - `overrides`: An optional object that allows for overriding the styles and props of child components. This is useful for dynamic customization based on server data.
 * - `data`: An optional generic type (`Data`) that contains the data required by the component. This could include text, numbers, or other types of data that the component needs to render.
 * - `events`: An optional object that defines the events and their corresponding actions (e.g., `onPress`, `onLongPress`).
 *
 * @template Data - The type of the data prop. Defaults to `never` if not specified.
 */
export type ConfigurableProps<Data, Style> = {
  readonly components?: Array<Component>
  readonly styles?: Style
  readonly overrides?: Overrides
  readonly data?: Data
  readonly events?: EventActions
}

Inflaters

Overview

In mobile development, creating responsive and dynamic user interfaces is a fundamental challenge. Developers typically rely on various layout patterns to organize and present content effectively. However, as applications grow in complexity, there's a growing need for UIs that can adapt in real time without requiring app updates.

Key challenges include:

  • Dynamically reordering components

  • Adding new components on the fly

  • Changing layout or placement without releasing a new version

The Flash framework addresses these needs by leveraging JSON-based layout configurations sent from the server.

To support these dynamic capabilities and maintain a robust UI system, we introduce Inflaters. Inflaters are responsible for understanding and rendering the JSON-based layout data received from the server. They are core components in the Flash system that enable powerful layout-driven configurations, such as reordering components or adding new ones dynamically.

This document explores Inflaters in more detail.


Available Layouts in Mobile App Development

Common layout types in mobile apps include:

  • Tab layouts

  • Stack layouts

  • List layouts

  • Scroll layouts

These help structure content and enhance user experience. However, adapting these layouts dynamically based on server data is where the real challenge lies.


Dynamic Layout Creation with Inflaters

Inflaters are responsible for interpreting the layout structure defined in server responses and rendering components accordingly. This allows UIs to be updated or changed remotely—without the need for app redeployment.

Flash provides several generic inflaters:

  • TabLayoutInflater: Renders child components as tabs

  • StackLayoutInflater: Renders components in a vertical stack

  • ListInflater: Renders components in a scrollable list (vertical, horizontal, or nested)

  • ScrollInflater: Renders components inside a scrollable container

These inflaters enable the Flash Client SDK to support flexible, layout-driven UIs across a variety of use cases.


Usage of Inflaters in a Component

As explained in the Flash Components section, every Flash-compatible component manages its own configuration. Inflaters play a key role when it comes to enabling dynamic layout capabilities, such as reordering elements or adding new ones.

Components that require any of these layout capabilities should use inflaters to interpret and apply the layout configuration provided by the server. The specific inflater used depends on the desired layout behavior.

To demonstrate this, we walk through an example of enhancing SportsListComponent using ListInflater, enabling it to support reordering and component insertion.

Example: Regular FlatList Component

Example: SportsListComponent Flash-Compatible with ListInflater

Example Implementation of ListInflater

To illustrate how inflaters work, let's look at an example of a ListInflater.

Type Definitions


Input Props and Data Binding Transformation

components

Inflaters receive a list of components from the server as part of the layout configuration. These components define the structure and content that should be rendered dynamically.

Data Prop

Data from existing APIs is transformed to match the format expected by inflaters. This transformation ensures that inflaters can properly associate data with each component and render them accordingly.

Style Prop

The style prop applies styling to the parent container of the inflater. This enables layout-level styling while maintaining flexibility for child components.

Overrides Prop

The overrides prop allows components to override specific layout or style properties of nested children. This adds a layer of customization on top of the default configuration.


Nested Scrollable Items

Inflaters also support nested scrollable elements. If a child item needs to scroll (horizontally or vertically), its layout should include the appropriate configuration from the server. This enables complex nested structures—like scrollable carousels inside a vertical list.


Inflater Implementations

Below is a summary of the current inflater types supported by the Flash framework:

  • ListInflater: Uses FlatList to dynamically render lists of child components.

  • ScrollInflater: Uses ScrollView to enable vertical or horizontal scrolling of child components.

  • TabInflater: Organizes child components into swipeable or clickable tabs.

  • StackInflater: Renders components in a vertically stacked format.

These inflaters provide the flexibility required to design dynamic, data-driven layouts with minimal hardcoding.

import React from 'react';
import { FlatList, View, Text, Pressable } from 'react-native';
import { ConfigurableProps } from '../../@types'; // Update the import path as needed

const SportsListComponent = (props: ConfigurableProps) => {
  const { components, style, data, onItemClick } = props;

  const renderItem = ({ item, index }) => {
    const child = components.find(child => child.name === item.componentName);
    if (!child) return null;

    const Component = getComponent(child.name);
    return (
      <Pressable onPress={() => onItemClick && onItemClick(item, index, child.name)}>
        <Component {...child.props} data={item.data} style={child.style} />
      </Pressable>
    );
  };

  return (
    <View style={style}>
      <FlatList
        data={data}
        renderItem={renderItem}
        keyExtractor={(item, index) => `${item.componentName}-${index}`}
      />
    </View>
  );
};

export default SportsListComponent;

// Helper function to get the component by name
const getComponent = (name) => {
  // Define your component mapping here
  const components = {
    ComponentA: ComponentA,
    ComponentB: ComponentB,
    // Add other components as needed
  };

  return components[name] || DefaultComponent;
};

// Define your default component if no match is found
const DefaultComponent = (props) => (
  <Text>Component not found</Text>
);

// Example components
const ComponentA = (props) => (
  <Text style={props.style}>{props.data}</Text>
);

const ComponentB = (props) => (
  <Text style={props.style}>{props.data}</Text>
);
import React from 'react';
import { ListInflater } from './ListInflater'; // Ensure ListInflater is imported correctly
import { ConfigurableProps } from '../../@types'; // Update the import path as needed

const SportsListComponent = (props: ConfigurableProps) => {
  return (
    <FlatListInflater
      components={props.components}
      style={props.style}
      data={props.data}
    />
  );
};

export default SportsListComponent;
import React, {useCallback} from 'react'

import {FlatList, ViewStyle} from 'react-native'

import {Component, PropData, FlatListInflaterProps} from '../../types/types'
import {getComponent} from '../../utils/render-utils'

/**
 * `FlatListInflater` component for rendering a dynamic list of components using `FlatList`.
 *
 * @template T - The type of data that each component will use, extending `PropData`.
 */
export const FlatListInflater = React.memo(
  <T extends PropData>({
    components,
    style,
    data,
    flatListProps, // Optional FlatList props without 'data' and 'renderItem'
  }: FlatListInflaterProps<T>): JSX.Element | null => {
    /**
     * Creates a React component based on the provided configuration and data.
     * The component configuration and optional data are passed to the appropriate UI component.
     *
     * @param {Component} component - The configuration object for the component.
     * @param {T | null} dataItem - The data item associated with the component, or `null` if no data is available.
     * @param {number | string} index - The unique index or key for the component, used for identifying the component in the list.
     *
     * @returns {JSX.Element} - The dynamically rendered component.
     */
    const createComponent = useCallback(
      (component: Component, dataItem: T | null, index: number | string) => {
        const RenderComponent = getComponent(component.name)

        return (
          <RenderComponent
            components={component.components} // Pass any child components
            style={component.styles} // Apply the component's styles
            overrides={component.overrides || {}} // Apply overrides if provided
            // @ts-ignore
            data={dataItem || {}} // Pass the component-specific data or an empty object if no data
            key={`${component.name}-${index || 'default'}-${
              component.id || Math.random()
            }`} // Unique key for the component
          />
        )
      },
      [], // Empty dependency array ensures the function is memoized and doesn't change between renders
    )

    /**
     * Memoized function to render each item in the `FlatList`.
     * Retrieves the associated data for the component and calls `createComponent`.
     *
     * @param {Object} item - The component configuration for the current list item.
     *
     * @returns {JSX.Element} - The dynamically rendered list item.
     */
    const renderItem = useCallback(
      ({item}: {item: Component}) => {
        // Retrieve the data for the component based on `dataId` or `name`
        const componentData = data
          ? item.dataId && item.dataId.trim().length > 0
            ? data[item.dataId]
            : data[item.name]
          : null

        // Create the component with the associated data
        return createComponent(item, componentData as T, 'single')
      },
      [data, createComponent], // Dependencies include `data` and `createComponent`
    )

    /**
     * Key extractor for `FlatList`, providing a unique key for each component.
     * Ensures that each component has a stable, unique key for efficient rendering.
     *
     * @param {Component} item - The component configuration.
     * @param {number} index - The index of the component in the list.
     *
     * @returns {string} - The unique key for the component.
     */
    const keyExtractor = useCallback(
      (item: Component, index: number) =>
        `${item.name}-${item.id || Math.random()}-${index}`,
      [], // Memoized function to ensure it doesn't change between renders
    )

    // If there are no components to render, return null to avoid rendering an empty list
    if (!components || components.length === 0) {
      return null
    }

    /**
     * Render the `FlatList` with the provided components and optional props.
     *
     * - `data`: The array of components to render.
     * - `renderItem`: The function used to render each item in the list.
     * - `keyExtractor`: Function that extracts unique keys for each item.
     * - `contentContainerStyle`: Optional style applied to the `FlatList` content container.
     * - `flatListProps`: Additional props to customize `FlatList` behavior, such as scrolling, layout, etc.
     */
    return (
      <FlatList
        data={components}
        renderItem={renderItem}
        keyExtractor={keyExtractor}
        contentContainerStyle={style as ViewStyle}
        testID="list-inflater-flatlist" // <-- Add this line for testing purposes
        {...flatListProps}
      />
    )
  },
)
/**
 * Represents the base configurable properties for an inflater component.
 *
 * Inflaters are used to dynamically manage and render components in different configurations.
 *
 * - `child`: An optional array of child components. This allows inflaters to manage nested components.
 * - `style`: An optional object defining the styling for the inflater component.
 * - `overrides`: An optional object for overriding the styles and props of child components within the inflater.
 * - `data`: An optional generic type (`T`) representing the data required by the inflater component.
 *
 * @template T - The type of the data prop. Defaults to `never` if not specified.
 */
export type BaseInflaterProps<T extends PropData | never = never> = {
  readonly components?: Array<Component>
  readonly style?: ViewStyle
  readonly overrides?: Overrides
  readonly data?: T
}

/**
 * Extends the base inflater properties with additional properties specific to `FlatListInflater`.
 *
 * - `flatListProps`: Optional props that will be passed to the underlying `FlatList` component.
 *   These props allow for customizing the behavior and appearance of the `FlatList`, such as enabling horizontal scrolling,
 *   setting the number of columns, or handling scroll events.
 *   Note that the `data` and `renderItem` props are managed internally by `FlatListInflater` and cannot be overridden.
 *
 * @template T - The type of the data prop used in the list, extending `PropData`.
 */
export type FlatListInflaterProps<T extends PropData> = BaseInflaterProps<T> & {
  readonly flatListProps?: Omit<FlatListProps<Component>, 'data' | 'renderItem'>
}

/**
 * Extends the base inflater properties with additional properties specific to TabInflater.
 *
 * This type does not add any new properties but maintains the structure of the base inflater.
 *
 * @template T - The type of the data prop used in the tabs.
 */
export type TabInflaterProps<T extends PropData> = BaseInflaterProps<T>

/**
 * Extends the base inflater properties with additional properties specific to StackInflater.
 *
 * This type does not add any new properties but maintains the structure of the base inflater.
 *
 * @template T - The type of the data prop used in the stack.
 */
export type StackInflaterProps<T extends PropData> = BaseInflaterProps<T>

/**
 * Extends the base inflater properties with additional properties specific to ScrollInflater.
 *
 * - `scrollViewProps`: An optional object containing props to be spread into the root `ScrollView`.
 *
 * @template T - The type of the data prop used in the scroll inflater.
 */
export type ScrollInflaterProps<T extends PropData> = BaseInflaterProps<T> & {
  readonly scrollViewProps?: ScrollViewProps
}
Drawing