Flash Client Sdk
  • Development
    • Flash Client
  • Understanding Configurability
  • Getting Started
    • Components
    • Inflaters
Powered by GitBook
On this page
  • Introduction
  • Component in React Native
  • Supporting Server-Driven Configuration
  • Handling Complex Layouts with Inflaters
  • Business Components
  • Configurability in Flash
Export as PDF

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.

PreviousFlash ClientNextGetting Started

Last updated 1 month ago