POST DIRECTORY
Category software development

When building cross-platform React Native Apps, at some point it is very likely you will want to use a native iOS UI component that is available as a React Native component, but is not available for the Android platform, or vice versa. Design pattern differences between iOS and Android are common to say the least, and there are not always easy implemented, pre-built solutions. Today we will look at a sample app where the usage of a native iOS UI component is desirable in our React Native app, but is not supported on the Android platform, and how we can move forward using Platform Specific Code.

The full application code for this sample app can be found on the haughtcodeworks github.

Before We Move Forward…

This post does not require you to code along, but if you would like to follow along with the code examples in the post, follow these steps:

$ git clone -b init [email protected]:HaughtCodeworks/react-native-platform-specific-sample-app.git
$ cd react-native-platform-specific-sample-app
$ npm install
$ expo start

Then start the Android emulator.

This post assumes you are familiar with React Native development and have setup both the iOS simulator and an Android emulator for local development.

Segemented Control IOS and the Android Platform

We have a sample application that renders one of two lists, each with selectable list items. The lists are rendered conditionally by a parent component, SelectView, that houses the SelectList component, as well as a control component titled SelectListControl.

SelectListControl will be the focus of this post. It uses a UISegmentedControl native iOS UI component via the React Native wrapper component SegmentedControlIOS. This is an iOS platform only UI component, as evidenced by the screenshots below:

 

Sample App Overview

Before executing our solution, let’s take a look at the components in our sample app. In total it contains eight components, one class based component that contains the app state, and seven functional components (including the root App component).

Component Hierarchy

For this sample app, we will use a single class based component, SelectView, which will contain all app state variables as well as the methods that will be passed as props to its child components.

The Select List Control Component

As stated above, we will be focusing on the SelectListControl component housing the SegmentedControlIOS component. It can be found in Components/SelectListControl/index.js:

...
const SelectListControl = ({
  segmentTitles,
  selectedIndex,
  updateSelectedIndex,
}) => (
  <View style={styles.controlContainer}>
    <SegmentedControlIOS
      onChange={event =>
        updateSelectedIndex(event.nativeEvent.selectedSegmentIndex)
      }
      selectedIndex={selectedIndex}
      values={segmentTitles}
      tintColor="#f46969"
    />
  </View>
);
...

The SelectListControl component has props as follows:

  • segmentTitles, the available values for the segment titles.
  • selectedIndex, the index of the currently active segment title.
  • updateSelectedIndex, the method that updates the selectedIndex when a segment title is pressed.

Platform Specific Code

React Native gives us the ability to conditionally render code based on platform two different ways: – via the Platform module. – or usage of Platform Specific Extensions.

We will implement a solution that employs both methods in this post, by using Platform Specific Extensions to create an Android-only SelectListControl file, and using the Platform module to conditionally handle styling.

Android-only Select List Control Implementation

Rather than creating a SegmentedControlIOS-like component for Android, we will use a more platform appropriate tabbed control for our lists. First we will create the Platform Specfic Android file, then refactor the shared SelectListControl/styles.js file to conditionally set the style of SelectListControl using the Platform module.

In the Components/SelectListControl/ directory create a new file titled index.android.js, React Native will use the file extension to correctly load in the relevant platform file.

Then, in SelectListControl/index.android.js, write out the imports, and the initial Android SelectListControl component along with its props and PropTypes:

import React from 'react';
import PropTypes from 'prop-types';

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

import styles from './styles';

const SelectListControl = ({
  segmentTitles,
  selectedIndex,
  updateSelectedIndex,
}) => {
  return (
    <View style={styles.controlContainer}>
      <Text>Tabbed Control</Text>
    </View>
  );
};

SelectListControl.propTypes = {
  segmentTitles: PropTypes.array,
  selectedIndex: PropTypes.number,
  updateSelectedIndex: PropTypes.func,
};

export default SelectListControl;

In SelectListControl/styles.js, import the Platform module from react-native, then set the style for controlContainer for each platform using Platform.select:

import { StyleSheet, Platform } from 'react-native';

export default StyleSheet.create({
  controlContainer: Platform.select({
    android: {
      backgroundColor: '#fafafa',
      minHeight: 48,
    },
    ios: {
      padding: 16,
      backgroundColor: '#fff',
    },
  }),
});

Platform.select, is a method on the Platform module. When given an object containing the ios and android keys, returns the value for the platform the app is running on.

You will immediately be able to see a change in the background color of the SelectListControl component, but the warning is still rendering:

 

Now that there is an Android specific SelectListControl file, this is a good time to update the iOS file using the Platform Specific Extension. Rename Components/SelectListControl/index.js to Components/SelectListControl/index.ios.js. Next we need to kill and restart the local development server so our javascript bundle compiles the Platform Specific Extensions and renders the correct components (you will also need to restart the Android emulator).

The Android emulator now looks like this:

 

Next, we will add the tabs to the Android SelectListControl component, then finish the styling.

To render the tabs, map over the segmentTitles, then add the logic needed to differentiate between the active and inactive tabs.

In SelectListControl/index.android.js, update SelectListControl to the following:

...
const SelectListControl = ({
  segmentTitles,
  selectedIndex,
  updateSelectedIndex,
}) => {
  const controlTabs = segmentTitles.map((segmentTitle, index) => {
    const isSelected = index === selectedIndex;
    return (
      <TouchableOpacity
        style={[
          styles.tabContainer,
          { backgroundColor: isSelected ? '#fff' : 'transparent' },
        ]}
        key={`${segmentTitle}${index}`}
        onPress={() => updateSelectedIndex(index)}
      >
        <Text
          style={[
            styles.tabText,
            { color: isSelected ? '#f46969' : 'rgba(0, 0, 0, 0.40)' },
          ]}
        >
          {segmentTitle}
        </Text>
      </TouchableOpacity>
    );
  });

  return <View style={styles.controlContainer}>{controlTabs}</View>;
};
...

Then complete the component style in SelectListControl/styles.js:

import { StyleSheet, Platform } from 'react-native';

export default StyleSheet.create({
  controlContainer: Platform.select({
    ios: {
      padding: 16,
      backgroundColor: '#fff',
    },
    android: {
      backgroundColor: '#fafafa',
      display: 'flex',
      flexDirection: 'row',
      minHeight: 48,
    },
  }),
  tabContainer: {
    alignItems: 'center',
    flex: 1,
    justifyContent: 'center',
  },
  tabText: {
    fontSize: 20,
    lineHeight: 24,
    letterSpacing: -0.4,
  },
});

And with that, the Android SelectListControl component is now complete and functional:

 

And We Are Done!

Platform Specific Code in React Native can be a powerful tool for handling scenarios within cross-platform apps where a UI component is only available for one platform, but can be extended to many other situations that may arise during development as well (for example, not all keyboard events correspond to both platforms…).

Have questions? Comments? Reach out to us at [email protected]!