Chapter 5. Lift Off! Sharing Your App

If you are just beginning to deploy native applications, plan for unexpected delays! For example, the deployment process with the Apple App Store requires several administrative hurdles that fall outside the scope of this cookbook, but are worth keeping in mind. Expect to deploy several iterations of your app before it’s ready for primetime.

Navigating each platform marketplace means acquainting yourself with new terminology and user interface particularities. The sections that follow include some tools and lessons learned for making this process as smooth as possible. I will also walk you through the testing model with the Apple App Store and then finish the chapter with some tips for dealing with platform-specific code that might surface as you deal with cross-platform delivery.

5.1 Automate Publishing Your App

You find yourself clicking through the Apple and Google stores over and over again to get through to beta or production. These user interfaces are error prone and mean that you can’t keep your store description in lock step with your app.

Problem

How can we keep as much of our App Store configuration versioned like any other source code if we want to send our build to a continuous integration service like bitrise? The answer is scripting our deployments. My personal favorite is fastlane.

Solution

fastlane is a powerful tool for simplifying the deployment of your application to the Google Play Store and the Apple App Store. At its core, fastlane is a collection of little tools that each do one thing well. They are written in Ruby, so you will need to have a recent version installed.

fastlane is a rubygem. This is akin to a package in NPM. Installing Ruby will be slightly different depending on your operating system. The Ruby Language download page includes instructions for all major operating systems. If you are a software developer using macOS, you will likely already be using Homebrew to install open source software easily.

Installing Ruby with Homebrew is as simple as:

$> brew install ruby
$> sudo gem install fastlane

The sudo command is required to install rubygems globally. You will be prompted to provide your system password to complete the installation. You should now be able to navigate to your project folder and type fastlane on the command line.

Setting up fastlane

fastlane recommends having a separate fastlane/ folder for both iOS and Android. Because React Native applications have a project root folder, I recommend centralizing all your fastlane configuration in a fastlane/ folder at the root of your project.

Warning

Semantic versioning (also called semver) is the widely practiced decision to use two points and three ordinals to denote the <major>.<minor>.<patch> version of an artifact. By default, Android Studio and Xcode will not set up this versioning structure for your application. Change it if you want fastlane to be able to automatically increment your build numbers.

If you decide to put a fastlane/ folder inside the android/ and ios/ folders, respectively, you can follow the steps in the command-line wizard:

$> cd ios/ # or android
$> fastlane
Could not find fastlane in current directory. Make sure to have your fastlane
configuration files inside a folder called "fastlane". Would you like to set
fastlane up? (y/n)

From there fastlane will detect what sort of project it is and create a Fastfile. The Fastfile will define different lanes: different deployment-related tasks, such as running a test suite, deploying to private beta, or publishing to a public audience.

Almost all the metadata fields (such as contact information, company name, demo account details, etc.) will be the same across platforms. Reduce copy/paste errors by storing these details in the appropriate file structure. For one project, my fastlane/ files are set up to handle the beta deployment on Android and iOS by simply running fastlane android beta or fastlane ios beta from the project root folder.

Here’s a sample fastlane/ folder structure that includes an en-CA localization:

├── Appfile
├── Deliverfile
├── Fastfile
├── Matchfile
├── README.md
├── metadata
│   ├── android
│   │   └── en-US
│   │       ├── full_description.txt
│   │       ├── images
│   │       │   └── icon.png
│   │       ├── short_description.txt
│   │       ├── title.txt
│   │       └── video.txt
│   ├── copyright.txt
│   ├── en-CA
│   │   ├── description.txt
│   │   ├── keywords.txt
│   │   ├── marketing_url.txt
│   │   ├── name.txt
│   │   ├── privacy_url.txt
│   │   ├── promotional_text.txt
│   │   ├── release_notes.txt
│   │   ├── subtitle.txt
│   │   └── support_url.txt
│   ├── primary_category.txt
│   ├── primary_first_sub_category.txt
│   ├── primary_second_sub_category.txt
│   ├── review_information
│   │   ├── demo_password.txt
│   │   ├── demo_user.txt
│   │   ├── email_address.txt
│   │   ├── first_name.txt
│   │   ├── last_name.txt
│   │   ├── notes.txt
│   │   └── phone_number.txt
│   ├── secondary_category.txt
│   ├── secondary_first_sub_category.txt
│   ├── secondary_second_sub_category.txt
│   └── trade_representative_contact_information
│       ├── address_line1.txt
│       ├── city_name.txt
│       ├── country.txt
│       ├── is_displayed_on_app_store.txt
│       ├── postal_code.txt
│       ├── state.txt
│       └── trade_name.txt
└── report.xml

Fastfile

fastlane looks for a Fastfile that describes the different commands available. When you type fastlane ios beta, you are calling the iOS platform and the beta lane.

Integration with a team chat service like Slack can keep everyone informed if a build fails. Consider that fastlane may also be run by a special server designed to do a deployment after code has been merged by a code-hosting platform like GitHub or BitBucket.

This project is called RNScratchpad and the Fastfile is stored in RNScratchpad/fastlane/Fastfile:

platform :ios do
  before_all do
    ENV["GYM_PROJECT"] = "ios/RNScratchpad.xcodeproj"
  end

  desc "Submit a beta to Apple TestFlight"
  lane :beta do
    match
    ensure_git_status_clean
    increment_build_number(xcodeproj: "ios/RNScratchpad.xcodeproj")
    gym(scheme: "RNScratchpad", export_xcargs: "-allowProvisioningUpdates")
    testflight
  end

  after_all do |lane|
    # This block is called, only if the executed lane was successful
    send_message_to_slack(
      "Successfully deployed new update",
      "ios",
      true
    )
  end

  error do |lane, exception|
    send_message_to_slack(
      exception.message,
      "ios",
      false
    )
  end
end

The :beta lane relies on a collection of functions that are part of the fastlane family. match, ensure_git_status_clean, increment_build_number, gym, and testflight are individual commands that run one after another. In some cases they have their own configuration files.

match relies on a Matchfile. match is a critical piece of tooling for managing iOS provisioning profiles and certificates. If you have multiple team members involved in deploying your application, I recommend following the match setup guide.

You can run each of the commands individually. For example, you can build your Xcode project with gym by running:

$> cd fastlane/
$> fastlane gym

ensure_git_status_clean will protect you from making the common mistake of deploying code that has not yet been committed to source control. increment_build_number will increase the build number automatically in the Info.plist, saving you from the manual step of increasing the number before being able to send your application to Apple. gym will trigger xcodebuild and compile your project. Using gym with a properly defined scheme will ensure that React Native is built for production. Because you are writing a JavaScript bundle, React Native needs to store a jsbundle file as part of the compilation process. This only happens in production.

Further down in the file, send_message_to_slack() is defined. Notice that the xcodeproj: symbol key is provided as a hint to fastlane. The slack_url would of course be specific to your team:

def send_message_to_slack(message, platform, success)
  if platform == 'android'
    build_number = get_version_code('android/app')
    version_name = get_version_name('android/app')
  elsif platform == 'ios'
    version = get_version_number(xcodeproj: 'ios/RNScratchpad.xcodeproj')
    build_number = get_build_number(xcodeproj: 'ios/RNScratchpad.xcodeproj')
  end
  slack(
    slack_url: "https://hooks.slack.com/services/TEAM_VAR/KEY/SERVICE",
    message: message,
    attachment_properties: {
        fields: [{
                      title: 'Version',
                      value: version_name,
                      short: true
                  },
                  {
                      title: 'Build Number',
                      value: build_number,
                      short: true
                  }]
    },
    success: success
  )
end

Defining store metadata

Each file provides one bit of text. For example, trade_representative_contact_information/city_name.txt simply includes:

Boston

Discussion

The first time you deploy your app in each store, you will have to go through an extensive registration process. This includes paying an annual fee, providing legal information, and categorizing your application. Unfortunately, the tools provided are the same whether you are going through them the first time or subsequent times.

See Also

Be patient with yourself when setting up tools like fastlane. They provide a lot of documentation as you move forward, but you should expect to run the build process dozens of times until it works perfectly for your environment and project requirements.

I recommend looking at the Getting Started guides. Once you’ve attempted to deliver your project to the Play Store or the App Store, slowly add more and more tools to your fastlane configuration. Look at the fastlane examples project configurations. The Mattermost mobile application also has a Fastfile configuration that is specific to React Native and worth reviewing.

5.2 Sharing Your iOS App with Beta Testers

Your pile of React components is shaping up to do something you and your users are excited about. While you could ask everyone to install React Native and download the source code to compile for themselves, why not use some of the tools Apple has provided to share your app with the world?

Problem

How does my team test my app? You will probably have a group of friends, colleagues, or investors that want to kick the tires on your new application before it’s launched to the broader public. If you want to appear on the Apple App Store, you will need to be acquainted with the different kinds of testers available to you in TestFlight, Apple’s beta testing toolkit for iOS.

Solution

iTunes Connect divides application testers into two categories: Internal Testers and External Testers. Internal Testers must have an iTunes Connect account, meaning that they:

  1. Must be invited to join iTunes Connect

  2. Must accept the invitation

Note

If you plan on testing your app on Android, each of your testers will need a Google account (@gmail.com or G Suite) to be included.

Once they are members of your iTunes Connect account, they can then be designated as Internal Testers for your app. They will then be sent another email where they will be invited to download TestFlight—an app-downloading service for beta software.

This multistep process is not simple, but it means that your app can be used without undergoing an Apple review process. External Testers, which can number in the thousands, can receive early builds only after they have undergone an Apple review step. This often requires having demo credentials and making sure that all Apple review guidelines are followed. Expect delays if you are using special app features like HealthKit or tracking location in the background. If your app allows users to share their own content, make sure you have support for flagging inappropriate content in place before the submission process. I would recommend getting any project managers or leadership roles set up as iTunes Connect users early on in the project so that they can see progress on their own personal devices.

Discussion

iTunes Connect is Apple’s response to two questions: “How do I test my app with beta testers?” and “How do I put my app on the App Store (and make money)?” There are a few steps to getting onto iTunes Connect: you (or your organization) need to register as Apple Developers. There is a yearly fee, and in the case of organizations, a D-U-N-S number (a widely used indexing number for entities) is also required. Once you are an Apple Developer, you should be able to visit https://developer.apple.com. If your application relies on additional features, such as push notification or the iAd platform, some further configuration will be required. Make sure you have an iCloud account under the Membership section of the platform. Once you have an Apple Developer account, you should be able to log in with this account in Xcode and also have access to iTunes Connect.

See Also

Apple describes the membership details in a fair amount of detail. My experience is that this whole process needs to be done a few times before it begins to click.

5.3 Configuring Application Settings

React Native enables easy cross-platform and cross-device development. You will already have at least a development environment and a production environment for your application. If you support tablet and phone, Android and iOS, and a development and production environment, then you have eight possible configurations for your application.

There are a number of tools for assisting in the complexities of multidevice (iOS/Android) and multienvironment (production/development) software. In Recipe 1.3, I touched on one of the mechanisms at your disposal, a platform suffix in a component. Another useful library is Platform, which makes handling platform-specific code easier to manage. The __DEV__ global constant can be used to determine whether we’re in a development or production environment. Lastly, the react-native-device-info package is an excellent one-stop shop for learning everything about a device. Let’s go through when you might use which tools.

Problem

What are some common challenges facing cross-platform development?

Spacing between views or sizing of typography may be different across platforms. You may find that iOS devices are rendering padding and margin properties inconsistently. You may also decide that you want to render a different sidebar depending on whether the app is running on a tablet or a phone. Finally, your configuration of logging and/or hostnames for servers may be dependent on the environment. For example, you may want the app to connect to http://localhost:8000 when in development and https://myapp.com/api when in production.

Solution

Let’s unpack these issues one by one. Start by distinguishing whether you are working with Android or iOS. Next, we will tackle production and development environments. Then we can further tailor our user experience by adjusting how components render on a tablet or a phone.

There are platform-specific styles

In Recipe 3.1 we looked at how you can build a global stylesheet for your application. At the top of the file, you can reference the Platform library that comes with react-native:

// updated styles.js
import { Platform, Dimensions } from 'react-native';
const { width, height } = Dimensions.get('window');

While defining your styles, you can now seamlessly tweak the user interface on a per-platform basis. Assume we’ve defined a default amount of spacing; now we can use these values to tailor our components:

const IOS_SPACING = 15;
const ANDROID_SPACING = 20;

// GLOBAL STYLES
export const globalStyles = {
  textHeader: {...fontSizes.H1,
    color: '#2A547A',
    paddingTop: 20,
    fontWeight: 'bold',
  },
  button: {
    backgroundColor: '#2A547A',
    minWidth: 40,
    ...Platform.select({
      android: { paddingTop: ANDROID_SPACING },
      ios: { paddingTop: IOS_SPACING }
    }),
  },
};

The resulting globalStyles.button will have slightly different padding for iOS and Android.

Android and iOS use different components

Sometimes a component used on iOS and Android needs to be completely different. For example, Material Design introduced the concept of a Floating Action Button (FAB), like the pink plus-sign button shown in Figure 5-1.

Image credit: Google
Figure 5-1. The Floating Action Button

This interaction is completely different than in the Apple User Experience Guidelines.

We can implement the same functionality with a completely different component design by using folders and platform suffixes:

components
├── actionButton
    ├── index.android.js
    └── index.ios.js

The <ActionButton /> is called in the main App.js file:

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

import ActionButton from './components/actionButton';

export default class App extends Component<{}> {

  constructor(props) {
    super(props);
    this.state = { times: 0 }
  }

  render() {
    return <View style={styles.container}>
      <View style={styles.header}></View>
      <ScrollView style={styles.scroll}>
        <Text style={styles.text} >Called: {this.state.times} Time(s)</Text>
      </ScrollView>
      <ActionButton onPress={ () => {
      this.setState((prevState) => {
      return { times: prevState.times + 1 };
      });
      } } />
    </View>
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 10,
  },
  text: {
    fontSize: 34
  }
  header: {
    backgroundColor: '#CACACA',
    height: 75
  },
  scroll: {
    height: 200,
  }
});

Each platform renders the <ActionButton /> differently:

// components/actionButton/index.ios.js
import React, { Component } from 'react';
import {
  StyleSheet,
  TouchableNativeFeedback,
  View,
  Text
} from 'react-native';

export default function ({ onPress }) {
  return <TouchableNativeFeedback onPress={onPress}>
    <View style={styles.button}>
      <Text style={styles.text}> Action! </Text>
    </View>
  </TouchableNativeFeedback>
}

const styles = StyleSheet.create({
  button: {
    borderColor: '#2A547A',
    borderWidth: 1,
    borderRadius: 5,
  },
  text: {
    textAlign: 'center',
    padding: 10,
    color: '#2A547A',
  }
});

The Android version of the <ActionBar /> uses Android-specific styles, like elevation, to create the raised button effect:

// components/actionButton/index.android.js
import React, { Component } from 'react';
import {
  StyleSheet,
  TouchableHighlight,
  Text
} from 'react-native';

export default function ({ onPress }) {
  return <TouchableHighlight style={styles.button} onPress={onPress}>
    <Text style={styles.text}>+</Text>
  </TouchableHighlight>
}

const styles = StyleSheet.create({
  button: {
    position: 'absolute',
    bottom: 50,
    right: 50,
    backgroundColor: '#ED5281',
    width: 60,
    height: 60,
    justifyContent: 'center',
    borderRadius: 30,
    elevation: 10,
  },
  text: {
    textAlign: 'center',
    color: '#FFF',
    fontSize: 30,
  }

});

Each platform will render based on a look that is more in line with the platform user experience guidelines (Figure 5-2).

The bottom button follows typical button designs for iOS; in Android, there is a Floating Action Button
Figure 5-2. Try to follow the user interface guidelines for each platform—here, the same action button is rendered entirely differently on iOS and Android

You are logging Redux events in development only

In Recipe 4.3, we used the redux-logger to display state changes in our application to the React Native developer console. The redux-logger will start logging all these events in your web browser’s developer console like in Figure 5-3.

View the output in in the development console
Figure 5-3. Output from redux-logger in the React Native debugger

Accessing these state changes on a device in production would be far beyond the scope of redux-logger. The __DEV__ will return true in development and false in any other case. Let’s turn it off in production. Update reduxStore.js like so:

import * as reducers from './src/reducers';
import { createStore, applyMiddleware, combineReducers, compose} from 'redux';
import { persistCombineReducers } from 'redux-persist';
import storage from 'redux-persist/es/storage';
import logger from 'redux-logger';
const config = {
  key: 'root',
  storage,
};
export default createStore(
  persistCombineReducers(config, reducers),
  __DEV__ ? {} : applyMiddleware(logger)
);

Determine whether the app is running on a tablet or a phone

In some cases you will want to render different application components based on whether the device is a tablet or a phone. Begin by installing react-native-device-info:

$> npm i --save react-native-device-info
$> react-native link

Here is an example of how you might use DeviceInfo.isTablet() to render the correct sidebar component:

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

import DeviceInfo from 'react-native-device-info';

export default class App extends Component {
  narrowSidebar() {
    return <View style={{width: 40, backgroundColor: '#333',
    flexDirection: 'column' }}>
        <View style={{height: 40, backgroundColor: '#666',
        flexDirection: 'row' }}></View>
        <View style={{flex: 0.7 }}></View>
      <View style={{flex: 0.1, backgroundColor: '#000' }}></View>
    </View>
  }



  wideSidebar() {
    return <View style={{ flex: 0.2, backgroundColor: '#333' }}>
      <View style={{ flex: 0.2, backgroundColor: '#666',
      flexDirection: 'row' }}>
        <View style={{ width: 50, padding: 5, backgroundColor: '#000' }}>
          <View style={{ width: 40, height: 40, borderRadius: 40,
            justifyContent: "center",
            backgroundColor: "#EA0" }}>
          </View>
        </View>
      </View>
      <View style={{ flex: 0.8 }}></View>
    </View>
  }

  render() {
    return (
      <View style={{ flexDirection: 'row', flex: 1, backgroundColor: '#FFF' }}>
        {DeviceInfo.isTablet() ? this.wideSidebar() : this.narrowSidebar()  }
        <View style={{ flex: 0.5, backgroundColor: '#FFF' }}>
          <Text>{DeviceInfo.isTablet() ? "Tablet" : "Phone"}</Text>
        </View>
        <View style={{ flex: 0.1, backgroundColor: '#FFA' }}></View>
      </View>
    );
  }
}

See Also

Another change you may wish to monitor is the device orientation. Fortunately every <View /> component includes an onLayout property. Learn more in a blog post by Matthew Sessions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.166.55