Updated March 29, 2016

Sharing Code between Android and iOS in React Native

Originally publish on medium.com.

One of the (many) advantages of writing an app with React Native is that you can write a mobile app for both iOS and Android from the same codebase. Something that, as designers and developers, I’m sure you’ve noticed is that they’ve got different design patterns to them. They’ve each got their own design guidelines (Android, iOS).

Ideally we could share 100% of the code between iOS and Android but at times this just doesn’t work… so how can we do it? React Native makes it very easy by providing an easy to use API — here are the docs on it. What’s it look like in practice though?

Setup

We won’t be using the react-native-meteor-boilerplate today because nothing is really Meteor specific but we’ll use the same React Native project structure. Let’s get set up…

  • react-native init CodeSharing
  • cd CodeSharing
  • mkdir app && cd app/
  • mkdir components/ && touch index.js

Then in index.js add

app/index.js

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

export default class CodeSharing extends Component {
  constructor(props) {
    super(props);

    this.state = {};
  }

  render() {
    return (
      <View style={styles.container}>
        <Text>CodeSharing App</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

Last part of our setup is to use app/index.js for both iOS and Android. This is our first step is code sharing. So for both index.ios.js and index.android.js change the files to

index.*.js

import React from 'react';
import { AppRegistry } from 'react-native';
import CodeSharing from './app/index';

AppRegistry.registerComponent('CodeSharing', () => CodeSharing);

So, even within setup you can see what code sharing starts to look like! Each platform has their own entry file and we want to share this initial screen. Okay, let’s jump into the real stuff now.

Platform.OS

Let’s leverage a simple piece of the Platform API by showing a tab bar on the bottom of the screen for iOS and the top of the screen for Android. They differentiate more than just where the tab bar is located but this will serve as a good example.

We’ll be using react-native-tabs so first we need to install it:

npm install --save react-native-tabs

Then in app/index.js let’s add a tab bar and basic styles.

app/index.js

import React, { Component } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import Tabs from 'react-native-tabs';

export default class CodeSharing extends Component {
  constructor(props) {
    super(props);

    this.state = {
      page: 'first',
    };
  }

  render() {
    const { page } = this.state;
    return (
      <View style={styles.container}>
        <Tabs
          selected={page}
          style={styles.tabbar}
          selectedStyle={{ color: 'red' }}
          onSelect={(el) => this.setState({ page: el.props.name })}
        >
          <Text name="first">First</Text>
          <Text name="second">Second</Text>
          <Text name="third">Third</Text>
        </Tabs>

        <Text>CodeSharing App</Text>
        <Text>{page}</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  tabbar: {
    backgroundColor: 'white',
    height: 64,
    borderTopColor: 'red',
    borderTopWidth: 2,
  },
});

0

Okay, both of the interfaces look exactly the same now. Let’s bump that tab bar to the top when the platform is Android.

app/index.js

import React, { Component } from 'react';
import { View, Text, StyleSheet, Platform } from 'react-native';
import Tabs from 'react-native-tabs';

/*
 * Removed for brevity
 */

render() {
  const { page } = this.state;
  const tabbarStyles = [styles.tabbar];
  if (Platform.OS === 'android') tabbarStyles.push(styles.androidTabbar);

  return (
    <View style={styles.container}>
      <Tabs
        selected={page}
        style={tabbarStyles}
        selectedStyle={{color:'red'}} onSelect={el=>this.setState({page:el.props.name})}
      >
          <Text name="first">First</Text>
          <Text name="second">Second</Text>
          <Text name="third">Third</Text>
      </Tabs>

      <Text>CodeSharing App</Text>
      <Text>{page}</Text>
    </View>
  )
}

const styles = StyleSheet.create({
  /*
   * Removed for brevity
   */
  androidTabbar: {
    top: 0,
    borderBottomColor: 'red',
    borderBottomWidth: 2,
    borderTopColor: 0
  }
});

Boom! Our toolbar now sits on the top of the screen when a user is on Android.

Platform Specific Components

Okay, that’s great but what if you need to write a platform specific component? Since this is a tutorial and I want to keep it short I’m going to build some buttons. Extremely simple example but it should illustrate the point!

Our buttons will work like so. On iOS the button will be sit just below the text stating which “page” we’re on. On Android it will sit at the bottom right of the screen. The common piece of this will be that the button stores in it’s state whether it is “pressed” or not.

First let’s install react-mixin so we can have mixin support while still using ES6.

npm install — save react-mixin@2

The button.common.js file simply handles the state for this component.

app/components/button.common.js

export default {
  getInitialState() {
    return {
      pressed: false,
    };
  },

  handlePress() {
    this.setState({ pressed: !this.state.pressed });
  },
};

For both iOS and Android we’re going to import react-mixin and our ButtonCommon logic that handles our state — something important to our iOS and Android version of the button.

Then, depending on the pressed state, we’ll either show a colored button or a more neutral button.

app/components/button.ios.js

import React, { Component } from 'react';
import { TouchableOpacity, Text, StyleSheet } from 'react-native';
import reactMixin from 'react-mixin';
import ButtonCommon from './button.common';

class Button extends Component {
  render() {
    const buttonStyle = [styles.button];
    if (this.state.pressed) buttonStyle.push(styles.buttonPress);

    return (
      <TouchableOpacity
        onPress={this.handlePress.bind(this)}
        style={buttonStyle}
      >
        <Text style={styles.text}>{this.props.text}</Text>
      </TouchableOpacity>
    );
  }
}

reactMixin.onClass(Button, ButtonCommon);
export default Button;

const styles = StyleSheet.create({
  button: {
    paddingVertical: 10,
    paddingHorizontal: 20,
    backgroundColor: '#0E7AFE',
  },
  buttonPress: {
    backgroundColor: '#C7C7C7',
  },
  text: {
    color: 'white',
  },
});

https://gist.github.com/spencercarli/023de9335a35e5b83e1799d5c845aed8#file-button-android-js

Lastly we’ll import our button and use it on the main page. You don’t need to specify the extension — React Native will determine which component to pull in depending on the platform.

app/index.js

import React, { Component } from 'react';
import { View, Text, StyleSheet, Platform } from 'react-native';
import Tabs from 'react-native-tabs';
import Button from './components/button';

/*
 * Removed for brevity
 */

render() {
  const { page } = this.state;
  const background = styles[page];
  const tabbarStyles = [styles.tabbar];
  if (Platform.OS === 'android') tabbarStyles.push(styles.androidTabbar);

  return (
    <View style={[styles.container, background]}>
      <Tabs
        selected={page}
        style={tabbarStyles}
        selectedStyle={{color:'red'}} onSelect={el=>this.setState({page:el.props.name})}
      >
          <Text name="first">First</Text>
          <Text name="second">Second</Text>
          <Text name="third">Third</Text>
      </Tabs>

      <Text>CodeSharing App</Text>
      <Text>{page}</Text>
      <Button text="Click Me!" />
    </View>
  )
}

And there you have it! This is obviously an extremely simple example and you could probably write it more succinctly but I simply wanted to set the stage for building larger cross platform components. This is a new concept to me still (in the past I’ve written entirely iOS apps with React Native) so any pointers are welcome!

React Native School Logo

React Native School

Want to further level up as a React Native developer? Join React Native School! You'll get access to all of our courses and our private Slack community.

Learn More