Create a Youtube-integrated application using React Native

Create a Youtube-integrated application using React Native

While building certain types of mobile apps, you might need to have media files displayed in the app. In this tutorial, we will see how to integrate media from YouTube into our mobile apps.

Introduction

While building certain types of mobile apps, you might need to have media files displayed in the app. In this tutorial, we will see how to integrate media from YouTube into our mobile apps.

What we'll build

We will build a simple mobile app using React Native called OurTube which will pull videos from the new trailers on Rotten Tomato YouTube channel via the Youtube API and display them to the user. After displaying, we should also be able to tap on a video and have it play inside the app.

Prerequisites

A basic understanding of React and React Native is required for this tutorial.

Getting started

Youtube is a video sharing website and it allows users to upload, rate, like, dislike, comment on videos and a lot more. Youtube also allows users to live stream videos. Youtube is also used widely for video blogging, educational purposes, movies, and trailers, and so on. With the Youtube API, users can find, watch, and manage content on YouTube. It also allows you to add YouTube functionality to your website and mobile apps.

Obtaining your YouTube API key

To get started, the first step is to obtain our developer API key from the developer console. If you prefer a video tutorial, you can find a detailed video tutorial on how to obtain your developer API keys for YouTube here.

After obtaining your API key that should look like AIzaSy****DjoCmWdzH*****cZYjOVg8o******, we require the playlist ID we would like to fetch. To get the playlist ID:

  • Go to Rotten Tomatoes TV channel YouTube page -> New Trailers Playlist
  • Copy the URL from the address bar e.g <a href="https://www.youtube.com/playlist?list=PLTovKDoAy18KZ6sUQcmK2RDQeYkm2xUNt" target="_blank">https://www.youtube.com/playlist?list=PLTovKDoAy18KZ6sUQcmK2RDQeYkm2xUNt</a> and the playlist ID will be the string after the list query in the URL which is PLTovKDoAy18KZ6sUQcmK2RDQeYkm2xUNt
Setting up our React Native project

To get started, follow the official documentation on how to set up your computer for React Native development. If you already have your computer set up, you need to initiate and create the application project.

    $ react-native init OurTube

Once that is completed, we need to compile and build the application to make sure everything is working fine.

    // For ios
    $ react-native run-ios

    // For Android
    $ react-native run-android

Configure routing using react-native-router-flux

React-native-router-flux is a simple navigation API on top of React Navigation and it allows you to declare all application routes in stack and scenes for easy management and configuration. To get started with react-native-router-flux:

    $ npm install react-native-router-flux --save

Go ahead and create a route file and configure all application routing.

    $ touch Route.js

Route.js should be in the root directory of our OurTube App and the contents will look like:

    // Route.js
    import React, { Component } from 'react';
    import {Platform} from 'react-native';
    // import components from react-native-router-flux
    import {Router, Stack, Scene} from 'react-native-router-flux';
    // import our screens as components 
    import Home from './screens/Home';
    import WatchVideo from './screens/Video';
    export default class Routes extends Component<{}> {
      render() {
        return(
          <Router>
              <Stack key="root">
                <Scene key="home" component={Home} title="Playlist Videos" />
                <Scene key="watchvideo" component={WatchVideo} title="View Video"/>
              </Stack>
          </Router>
          )
      }
    }

The Router is the main routing component while a Stack is a group of Scenes with it's own navigator, so you can have multiple stacks for navigating. A Scene is the basic routing component for the main router and all Scene components require a key prop that must be unique and a key prop is used to call the screen transition and must be unique to all screens. We have created two scenes called home and watchvideo. The scenes will be called as actions in our Home and Video screens. More information can be found in the official documentation of React Native Router Flux. Update App.js file to look like:

    // App.js
    import React, { Component } from 'react';
    import {
      StyleSheet,
      View,
      StatusBar,
    } from 'react-native';
    import Routes from './Route';
    export default class App extends Component<{}> {
      render() {
        return (
          <View style={styles.container}>
            <StatusBar
              backgroundColor="#fff"
              barStyle="dark-content"
            />
            <Routes/>
          </View>
        );
      }
    }
    const styles = StyleSheet.create({
      container : {
        flex: 1,
      }
    }); 

Creating UI components

React Native offers inbuilt UI components and we'll be leveraging these inbuilt components. We will make use of components such as the FlatList, Text, and so on. Let's go ahead to create our project directories and files:

    $ mkdir screens && cd screens
    $ touch Home.js Video.js

``js // screens/Home.js import React, { Component } from 'react'; import { StyleSheet, SafeAreaView, FlatList, Text, TouchableOpacity } from 'react-native'; import {Actions} from 'react-native-router-flux'; const MAX_RESULT = 15; const PLAYLIST_ID = "PLScC8g4bqD47c-qHlsfhGH3j6Bg7jzFy-"; const API_KEY = ""; export default class Home extends Component<{}> {

  home(){
    Actions.home();
  }
  watchVideo(video_url){
    Actions.watchvideo({video_url: video_url});
  }
  componentWillMount() {
    this.fetchPlaylistData();
  }
  fetchPlaylistData = async () => {
    const response = await fetch(`https://www.googleapis.com/youtube/v3/playlistItems?playlistId=${PLAYLIST_ID}&maxResults=${MAX_RESULT}&part=snippet%2CcontentDetails&key=${API_KEY}`);
    const json = await response.json();
    this.setState({ videos: json['items']});
    console.log(this.state.videos)
  };
  constructor(props) {
    super(props);
    this.state = {
      videos: [],
    }
  }
  render() {
    const videos = this.state.videos;
    return (
      <SafeAreaView style={styles.safeArea}>
            <FlatList
              data={this.state.videos}
              keyExtractor={(_, index) => index.toString()}
              renderItem={
                ({item}) => 
                <TouchableOpacity
                    style={styles.demacate}
                    onPress={() => this.watchVideo(item.contentDetails.videoId)}
                >
                <Text 
                  style={styles.item} 
                > 
                {item.snippet.title} 
                </Text>
                </TouchableOpacity>
              }
            />
      </SafeAreaView>
    );
  }
}
const styles = StyleSheet.create({
  safeArea: {
    flex: 1,
    backgroundColor: '#fff'
  },
  demacate: {
    borderBottomColor: 'blue',
    borderBottomWidth: 2,
    borderRadius:10
  },
  item: {
    padding: 10,
    fontSize: 12,
    height: 44,
  },
});

The first thing we do in the `Home.js` file is to import all the necessary components to build the home screen. Like the variable names imply, `MAX_RESULT` is the number of videos we want to return from the youtube API, `PLAYLIST_ID` is a string of the youtube playlist we want to fetch and `API_KEY` will hold our Youtube API access key.

The `home()` and `watchVideo()` functions will call navigation actions that allow us to navigate from page to page. The `fetchPlaylistData()` is an asynchronous function that allows us to fetch data from the Youtube API and `componentWillMount()` allows us to fetch the async data after the screen is rendered. 

The response data is converted to JSON and stored in the videos state and will be passed to our component during rendering. 

In the `render()` we define a prop that holds data fetched such as video id from our Youtube API and we pass it to the videos page as a `video_url` prop so we don't have to do a network fetch to get the same data on the next screen. 

Our `render()` function renders the screen and we use the SafeAreaView component to handle the screens of newer devices such as the iPhone X, XR and higher so the screen doesn't overlap. We use the `Flatlist` component to render our data from the `videos` state using the `renderItem` prop.

```js
    // screens/Video.js
    import React, { Component } from 'react';
    import { StyleSheet, SafeAreaView, View, WebView } from 'react-native';
    import {Actions} from 'react-native-router-flux';
    export default class WatchVideo extends Component<{}> {

      home(){
        Actions.home();
      }
      videos(){
        Actions.videos();
      }
      constructor(props) {
        super(props);
        console.log(this.props);
      }
      render() {
        return (
          <SafeAreaView style={styles.safeArea}>
              { 
                <WebView
                  source={{ uri: "https://www.youtube.com/embed/"+this.props.video_url}}
                  startInLoadingState={true} 
                />
              }
          </SafeAreaView>
        );
      }
    }
    const styles = StyleSheet.create({
      safeArea: {
        flex: 1,
        backgroundColor: '#fff'
      }
    });

In the Video.js file, we also import all the necessary components to build our interface and likewise, create routing actions so we can navigate back to our home screen. In the render() function, the data we stored in the video_url props is accessed and rendered using the Webview video component. The component is customizable and allows us to define the URL source, loaders, and so on.

Building our app

To build and compile our code on iOS:

    $ react-native run-ios

To build and compile our code on Android:

    $ react-native run-android

After build is successful, your application should look like:

  • Home Screen
  • Video Screen
Conclusion

The YouTube API is robust and allows us to watch videos, get video metadata, and so on. The data can be integrated with any mobile development framework and as seen in OurTube app using React Native. OurTube app is in a simple version right now, but you can go ahead to add some more functionalities such as liking and disliking videos, reporting comments, and so on by reading the Google Developer guide for Youtube in order to build complex and robust applications. The codebase to this tutorial is available in a public GitHub repository. Feel free to play around with it.

Thanks for reading ❤

How to build a news app with JavaScript and React Native

How to build a news app with JavaScript and React Native

How to build a news app with JavaScript and React Native

Requirements for building the app:

  • A basic understanding of the JavaScript language.
  • Node.js, and react native.
  • Libraries used: moment, react-native, react-native-elements.

If you’re not familiar with these resources, don’t worry — they are quite easy to use.

The topics we will cover in the post are:

  • A basic understanding of the JavaScript language.
  • Node.js, and react native.
  • Libraries used: moment, react-native, react-native-elements.

And more…so let’s get started!

You can find the full project repo HERE.

News API

A simple and easy-to-use API that returns JSON metadata for headlines and articles live all over the web right now. — NewsAPI.org
First, you should go ahead and sign up for News Api to get your free apiKey (your authentication key).

Create a new React Native project, and call it news_app (or whatever you want). In the project directory, make a new folder and call it src . In srccreate a folder an name it components . So your project directory should look something like this:

In the src folder, create a new file called news.js . In this file we are going to fetch the JSON that contains the headlines from the News API.

news.js
const url =
  "https://newsapi.org/v2/top-headlines?country=us&apiKey=YOUR_API_KEY_HERE";

export async function getNews() {
  let result = await fetch(url).then(response => response.json());
  return result.articles;
}

Make sure you replace YOUR_API_KEY_HERE with your own API key. For more information about the News API, go to newsapi docs.

Now we declare the getNews function, which is going to fetch the articles for us. Export the function so we can use it in our App.js file.

App.js
import React from 'react';
import { FlatList } from 'react-native';

// Import getNews function from news.js
import { getNews } from './src/news';
// We'll get to this one later
import Article from './src/components/Article';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { articles: [], refreshing: true };
    this.fetchNews = this.fetchNews.bind(this);
  }
  // Called after a component is mounted
  componentDidMount() {
    this.fetchNews();
   }

  fetchNews() {
    getNews()
      .then(articles => this.setState({ articles, refreshing: false }))
      .catch(() => this.setState({ refreshing: false }));
  }

  handleRefresh() {
    this.setState(
      {
        refreshing: true
    },
      () => this.fetchNews()
    );
  }

  render() {
    return (
      <FlatList
        data={this.state.articles}
        renderItem={({ item }) => <Article article={item} />}
        keyExtractor={item => item.url}
        refreshing={this.state.refreshing}
        onRefresh={this.handleRefresh.bind(this)}
      />
  );
  }
}

In the constructor, we define the initial state. articles will store our articles after we fetch them, and refreshing will help us in refresh animation. Notice that I setrefreshing to true, because when we start the app, we want the animation to start while we load the articles.

componentDidMount is invoked immediately after a component is mounted. Inside it we call the fetchNews method.

componentDidMount() {
  this.fetchNews();
}

In fetchNews we call getNews() which returns a promise. So we use the .then() method which takes a callback function, and the callback function takes an argument (the articles).

Now assign the articles in the state to the articles argument. I only typed articles because it’s a new ES6 syntax that means { articles: articles } , and we set refreshing to false to stop the spinner animation.

fetchNews() {
  getNews().then(
      articles => this.setState({ articles, refreshing: false })
  ).catch(() => this.setState({ refreshing: false }));
}

.catch() is called in rejected cases.

handleRefresh starts the spinner animation and call fetchNews(). We pass () => this.fetchNews() , so it’s called immediately after we assign the state.

handleRefresh() {
  this.setState({ refreshing: true },() => this.fetchNews());
}

In the render method, we return a FlatList element. Then we pass some props. data is the array of articles from this.state. The renderItem takes a function to render each item in the array, but in our case it just returns theArticle component we imported earlier (we’ll get there). And we pass the article item as a prop to use later in that component.

Article.js

In src/components create a new JavaScript file and call it Article.js.

Let’s start by installing two simple libraries using npm: react-native-elements, which gives us some premade components we could use, and moment that will handle our time.

run using terminal/cmd:

npm install --save react-native-elements moment

In Article.js:

import React from 'react';
import { View, Linking, TouchableNativeFeedback } from 'react-native';
import { Text, Button, Card, Divider } from 'react-native-elements';
import moment from 'moment';

export default class Article extends React.Component {
  render() {
    const {
      title,
      description,
      publishedAt,
      source,
      urlToImage,
      url
    } = this.props.article;
    const { noteStyle, featuredTitleStyle } = styles;
    const time = moment(publishedAt || moment.now()).fromNow();
    const defaultImg =
      'https://wallpaper.wiki/wp-content/uploads/2017/04/wallpaper.wiki-Images-HD-Diamond-Pattern-PIC-WPB009691.jpg';

    return (
      <TouchableNativeFeedback
        useForeground
        onPress={() => Linking.openURL(url)}
      >
        <Card
          featuredTitle={title}
          featuredTitleStyle={featuredTitleStyle}
          image={{
            uri: urlToImage || defaultImg
          }}
        >
          <Text style={{ marginBottom: 10 }}>
            {description || 'Read More..'}
          </Text>
          <Divider style={{ backgroundColor: '#dfe6e9' }} />
          <View
            style={{ flexDirection: 'row', justifyContent: 'space-between' }}
          >
            <Text style={noteStyle}>{source.name.toUpperCase()}</Text>
            <Text style={noteStyle}>{time}</Text>
          </View>
        </Card>
      </TouchableNativeFeedback>
    );
  }
}

const styles = {
  noteStyle: {
    margin: 5,
    fontStyle: 'italic',
    color: '#b2bec3',
    fontSize: 10
  },
  featuredTitleStyle: {
    marginHorizontal: 5,
    textShadowColor: '#00000f',
    textShadowOffset: { width: 3, height: 3 },
    textShadowRadius: 3
  }
};

There is a lot going on here. First, we start by destructuring the articleprop and the styles object defined below the class.

In render we define time to store the time for when the article was published. We use the moment library to convert the date to the time passed since then, and we pass publishedAt or time from now if publishedAt is null.

defaultImg is assigned an image URL in case the URL of the article image is null.

The render method returns TouchableNativeFeedback (use TouchableOpacityinstead if it does not work on your platform) to handle when the user presses the card. We pass it some props: useForground which tells the element to use the foreground when displaying the ripple effect on the card, and onPress , which takes a function and executes it when the user presses the card. We passed () => Linking.openUrl(url) which simply opens the URL to the full article when we press the card.

The card takes three props: featuredTitle which is just a fancy title placed over the image you could use title instead if you want, featuredTitleStyle to style it, and image which is the article image from the article prop. Otherwise, if its null , it’s going to be the defaultImg.

..
  featuredTitle={title}
  featuredTitleStyle={featuredTitleStyle}
  image={{ uri: urlToImage || defaultImg }}
..

As for the text element, it will hold the description for the article.

<Text style={{ marginBottom: 10 }}>{description}

We added a divider to separate the description from time and source name.

<Divider style={{ backgroundColor: '#dfe6e9' }} />

Below the Divider , we have a View that contains the source name and the time the article was published.

..
<View 
  style={{ flexDirection: ‘row’, justifyContent: ‘space-between’ }} > 
  <Text style={noteStyle}>{source.name.toUpperCase()}</Text>
  <Text style={noteStyle}>{time}</Text>
</View>
..

After the class, we defined the styles for these components.

Now if we run the app:

There you go! The source code for the app is available on GitHub HERE you can improve upon it and make a pull request😄.

Accepting payments in React Native

Accepting payments in React Native

In this tutorial, we’ll be looking at how to accept payments within a React Native app.

In this tutorial, we’ll be looking at how to accept payments within a React Native app.

An important aspect when creating an app or website is the ability to accept payments from its customers. Platforms such as the web, Android, and iOS already have well-supported APIs for payment gateways such as Stripe. But what about cross-platform frameworks like React Native?

Prerequisites

Basic knowledge of React Native is required. Although we’ll be using a bit of PHP, it’s optional because the code will be self-explanatory.

Development environment

You need to have PHP and MySQL installed on your machine. The easiest way to get the development environment is through Laravel Homestead. If you’re on Mac, you can get up and running even faster with Laravel Valet.

You also need a MySQL database manager for creating the database:

You also need to have an ngrok account for exposing the server to the internet.

Developer accounts

We will be using Stripe to process payments, so a Stripe account is required.

Optionally, you will also need to have the following if you want to use Google Pay and Apple Pay in production:

  • Google Play developer console account - you can actually use the Tipsi-Stripe library without a developer account if you use the test environment. But if you want to use it in production, you’ll need to have a Google Play developer console account which can accept payments.
  • Apple developer account - you need this if you want to use Apple Pay with Tipsi-Stripe. At the time of writing this tutorial, there are only a handful of countries in which Sandbox testing for Apple Pay is supported. This means you’ll have to use real credit cards for testing if you’re not living in one of those countries. As this tutorial won’t be covering how to set up Apple Pay on your developer account, be sure to read the Getting Started with Apple Pay guide.

Package versions

The following package versions are used in the app:

  • React Native 0.57.8
  • Tipsi-stripe 6.1.2

While the following are used in the backend:

  • Lumen framework 5.7
  • Stripe PHP 6.28

To ensure compatibility, start with the versions above before upgrading to the latest versions.

App overview

We’ll create a simple app which displays a product to be bought. The user can then pay for it with the credit card they have added to their Google Account. We will use Google Pay as a means for accepting the payment, and Stripe for processing it. If you’ve previously worked with Stripe for the web, the process is pretty much the same. Tipsi-Stripe provides a way to use either Google Pay, Apple Pay, or its custom React component for accepting the payment details. You will then use any of these methods to generate the token which you will submit to the app’s backend. This token allows you to charge the card added by the user.

Here’s what the app will look like:

You can find the source code on this GitHub repo.

Building the app

Start by creating a new React Native project and installing the dependencies:

    react-native init RNPay
    cd RNPay
    yarn add tipsi-stripe react-native-config axios
    react-native link

Once that’s done, add the config for enabling the Google Wallet API in the android/app/src/main/AndroidManifest.xml file. This allows you to use Google Pay within the app:

    <application>
      <meta-data
        android:name="com.google.android.gms.wallet.api.enabled"
        android:value="true" />
    </application>

Next, update the android/app/build.gradle file and add the path to React Native Config:

`apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"`

For iOS, please refer to the documentation:

Next, create a .env file at the root of the project directory and add your Stripe publishable key. If you’re testing, this can be the test credentials provided by Stripe. You can find your API keys here. Be sure to toggle Viewing Test Data if you want your test credentials:

    STRIPE_PUBLISHABLE_KEY=YOUR_STRIPE_PUBLISHABLE_KEY

Once that’s done, you’re now ready to work on the code. Start by opening the App.js file and add the following:

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

    import stripe from 'tipsi-stripe';
    import Config from 'react-native-config';

    import ItemBox from './src/components/ItemBox';
    import pay from './src/helpers/pay';

    stripe.setOptions({
      publishableKey: Config.STRIPE_PUBLISHABLE_KEY,
      androidPayMode: 'test', // test || production
    });

The most important part in the above code is the setting of the options for Stripe. The publishableKey is basically the same key as the one you put on your JavaScript files when working on the web. androidPayMode is the mode to be used by Google Pay. test means that the requirements for using it won’t be as tight as when you’re on production. For example, the app doesn’t need to be uploaded in the Google Play Store. Your Google Play console developer account also don’t need to have Google Pay enabled. In fact, you don’t even need a developer account in order to try it out. You can find more info about it here.

Next, initialize the data to be used throughout the app. This includes the user’s access token which is used for authenticating the user’s request when we hit the endpoint for creating a charge. To simplify things we’re simply going to hardcode an existing access token that’s already in the database. I’ll show you how to create the database later on when we get to the backend:

    export default class App extends Component {

      constructor(props) {
        super(props);
        this.access_token = "AN EXISTING USER'S ACCESS TOKEN FROM YOUR DB";
        this.currency_code = 'USD'; // the currency to be used for processing the transaction
        // item data
        this.item = {
          title: 'Loop 720',
          price: 1,
          image: require('./src/images/loop720.jpg')
        };
      }

      state = {
        isPaying: false, // whether the user is currently paying for something 
        canPay: false // whether the user's device has the ability to pay using Google Pay
      }

      // next: add componentDidMount
    }

    const styles = {
      container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#FFF',
      }
    };

Next, we check if the user’s device can make payments with Google Pay:

    async componentDidMount() {
      const device_supported = await stripe.deviceSupportsAndroidPay();
      const can_make_android_pay_payments = await stripe.canMakeAndroidPayPayments();

      if (device_supported && can_make_android_pay_payments) {
        this.setState({
          canPay: true
        });
      }
    }

For the app’s UI we’re simply going to render a single item:

    render() {
      return (
        <View style={styles.container}>
          <ItemBox
            {...this.item}
            canPay={this.state.canPay}
            isPaying={this.state.isPaying}
            payAction={this.payItem} />
        </View>
      );
    }

Here’s the code for the ItemBox component:

    // src/components/ItemBox.js
    import React, { Component } from 'react';
    import { View, Text, Button, ActivityIndicator, Image } from 'react-native';

    const ItemBox = ({ title, price, image, canPay, isPaying, payAction }) => {

      return (
        <View>
          <Image
            style={styles.image}
            source={image} />

          <View style={styles.textContainer}>
            <Text style={styles.title}>{title}</Text>
          </View>

          <View style={styles.textContainer}>
            <Text style={styles.price}>${price.toFixed(2)}</Text>
          </View>

          {
            isPaying &&
            <ActivityIndicator size="large" color="#0000ff" />
          }

          <View style={styles.buttonContainer}>
          {
            canPay && !isPaying &&
            <Button
              onPress={payAction}
              title="Buy Now"
              color="#841584"
            />
          }

          {
            !canPay &&
            <View style={styles.alertContainer}>
              <Text style={styles.errorText}>Can't accept payments</Text>
            </View>
          }
          </View>

        </View>
      );

    }

    export default ItemBox;

    const styles = {
      image: {
        width: 170,
        height: 150
      },
      textContainer: {
        alignItems: 'center'
      },
      title: {
        fontSize: 20
      },
      price: {
        fontSize: 23,
        fontWeight: 'bold'
      },
      buttonContainer: {
        margin: 10
      },
      alertContainer: {
        width: 150
      },
      errorText: {
        color: 'red'
      }
    };

Back to the App.js file, add the function for paying for the item. This gets executed when the Buy Now button from the ItemBox component is clicked:

    // App.js
    payItem = async () => {

      this.setState({
        isPaying: true // show loader instead of the button
      });

      const token = await stripe.paymentRequestWithNativePay({
        total_price: this.item.price.toFixed(2),
        currency_code: this.currency_code,
        line_items: [
          {
            currency_code: this.currency_code,
            description: this.item.title,
            total_price: this.item.price.toFixed(2),
            unit_price: this.item.price.toFixed(2),
            quantity: '1',
          }
        ]
      });

      // next: add code for committing the charge into the server
    }

If you want to capture shipping information, you need to set the shipping_address_required option and set its value to true. Optionally, you can also set the shipping_countries to an array of country codes to limit the countries you want to ship to:

    shipping_countries: ['US', 'PH', 'SG']

Here’s what the value of token looks like when the request is successful:

    {  
       "card":{  
          "currency":null,
          "fingerprint":"xxxxxxxxxx",
          "funding":"credit",
          "addressZip":null,
          "brand":"MasterCard",
          "cardId":"card_xxxxxxxxxxx",
          "number":null,
          "addressState":null,
          "country":"US",
          "cvc":null,
          "expMonth":7,
          "addressLine1":null,
          "expYear":3040,
          "addressCountry":null,
          "name":"Wern Ancheta",
          "last4":"11xx",
          "addressLine2":null,
          "addressCity":null
       },
       "created":1546997773000,
       "used":false,
       "extra":{  
          "shippingContact":{  
             "postalCode":"2500",
             "name":"Wern Ancheta",
             "locality":"San Juan",
             "countryCode":"PH",
             "administrativeArea":"La Union",
             "address1":"Forest street"
          },
          "billingContact":{  

          }
       },
       "livemode":false,
       "tokenId":"tok_xxxxxxxx"
    }

For more examples, please refer to the example folder in Tipsi-Stripe’s GitHub repo. And for more info on how to use them, check out the following:

Do note that if you plan on using the paymentRequestWithNativePay method, you have to first determine the platform the app is running on because the options you need to pass to the method will differ based on the platform. This is one advantage of the paymentRequestWithCardForm because of its platform-agnostic API.

Next, send the request for creating a charge to the server:

    const response = await pay(this.item.price, this.item.title, this.access_token, token.tokenId);
    if (response) {
      Alert.alert("Done!", "Payment successful");
    } else {
      Alert.alert("Error occurred", "Something went wrong while processing payment. Please try again.");
    }

    this.setState({
      isPaying: false // show the Buy Now button again
    });

Next, create a src/helpers/pay.js file and add the following. This sends a POST request to the server which includes the relevant details for the payment transaction:

    import axios from 'axios';

    const endpoint = 'https://YOUR_NGROK_URL/charge';

    const pay = async (amount, description, access_token, token_id) => {
      const data = {
        'amount': amount,
        'description': description,
        'access_token': access_token,
        'token_id': token_id // the token generated by Stripe
      };

      const headers = {
        'Content-Type': 'application/json',
      };

      let response = false;
      try {
        let response_data = await axios.post(endpoint, data, { headers });
        return true;
      } catch (e) {
        console.log('server error: ', e);
      }

      return response;      
    }

    export default pay;

In the above code, we’re only sending four pieces of data to the server. But you can actually send more if you like. Note that you can actually fetch the same data returned by stripe.paymentRequestWithNativePay method call in the server by making a request to the Stripe API. This means you don’t actually need to submit things like the shipping address or the customer’s name in your request. So most likely, the additional data you submit here will be specific to your application.

Add the backend code

This part assumes that you’ve already set up your machine with either Laravel Homestead or Laravel Valet. This will give you the composer command which is used below to generate a new Lumen project:

    composer create-project --prefer-dist laravel/lumen RNPayBackend

If you can’t run composer globally, be sure to move it to your local bin folder or add it to your PATH.

Next, navigate inside the generated RNPayBackend directory and add the database config:

    DB_CONNECTION=mysql
    DB_HOST=127.0.0.1
    DB_PORT=3306
    DB_DATABASE=rnpay
    DB_USERNAME=your_username
    DB_PASSWORD=your_password

The above config assumes that you have already created a database using a database manager of your choice. So be sure to create one with the same name as the value given to DB_DATABASE.

Also, add your Stripe secret key to the .env file:

    STRIPE_SECRET_KEY=YOUR_STRIPE_SECRET_KEY

Next, create a new database migration file. These files allow you to write some code for updating the database structure:

    php artisan make:migration create_users_table

Navigate inside the database/migrations directory and you will see the generated file. Add the following code to it:

    <?php
    use Illuminate\Support\Facades\Schema;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Database\Migrations\Migration;

    class CreateUsersTable extends Migration
    {
        public function up()
        {
            Schema::create('users', function (Blueprint $table) {
                $table->increments('id');
                $table->string('access_token'); // the user's access token
                $table->string('stripe_customer_id')->nullable(); 
                $table->timestamps(); // created_at and updated_at timestamp fields
            });
        }

        public function down()
        {
            Schema::dropIfExists('users');
        }
    }

Migrate the database using the new file. This creates a users table with the fields you specified above:

    php artisan migrate

Next, access the database using a MySQL database manager and add a dummy data in the users table. Leave the stripe_customer_id as blank and copy the access_token to the App.js file.

Next, install the Stripe PHP library:

    composer require stripe/stripe-php

While that’s doing its thing, update the bootstrap/app.php file and uncomment the following line. This enables us to use Facades. In simple terms, Facades are easy to remember class names which allow us to access underlying functionality such as logging or fetching data from the database:

    $app->withFacades();

Next, add the route for processing payments to the routes/web.php file. This is the endpoint that we’re hitting in the app earlier. This uses a controller to process the request:

    $router->post('/charge', '[email protected]');

Create the controller at app/Http/Controllers/PaymentController.php and add the following:

    <?php

    namespace App\Http\Controllers;
    use DB; // for talking to the database
    use Illuminate\Support\Facades\Log; // for logging
    use Illuminate\Http\Request; // for getting request data

    class PaymentController extends Controller
    {

      public function __construct() {
        \Stripe\Stripe::setApiKey(getenv('STRIPE_SECRET_KEY')); 
      }

      // next: add code for creating a charge
    }

In the above code, we’ve added a __construct method. This sets the Stripe API key that we’re going to use. This gets executed everytime any of the methods in the PaymentController gets called.

Next, add the method for creating a charge. This gets executed every time a POST request is made to the /charge route. Here we pass the data which came from the app to Stripe’s API method for creating a charge. If the charge is successful, we return the success response to the app:

    public function createCharge(Request $request) {

      // get the data that was passed from the app  
      $amount = (int) $request->input('amount') * 100; // amount should be in cents
      $description = $request->input('description');
      $access_token = $request->input('access_token');
      $token = $request->input('token_id');

      // get the Stripe customer ID based on the access token
      $user = DB::table('users')
          ->where('access_token', $access_token)
          ->select('id', 'stripe_customer_id')
          ->first();

      // construct the data required by Stripe for creating a charge
      $payment = [
        'amount' => $amount, 
        'currency' => 'usd',
        'description' => $description,
        'customer' => $user->stripe_customer_id
      ];

      if (empty($user->stripe_customer_id)) {
        // next: add code for creating a Stripe customer
      }

      try {
        $charge = \Stripe\Charge::create($payment);
        return ['status' => 'ok']; // if the charge was successful
      } catch (\Exception $e) {
        Log::info("Cannot create charge for Stripe customer: " . $user->id);
      }

      return ['status' => 'not_ok']; // if the charge wasn't successful
    }

If there’s no Stripe customer ID associated with the user, it means that a Stripe customer hasn’t been created for the user yet. So to associate a customer with the provided payment info, we need to make a separate request to the Stripe API to create a customer. This is a necessary step for associating a payment from a specific customer. Even though the Stripe API allows you to just pass the $token_id when creating a charge, it isn’t really recommended. Especially if you expect to receive payments from the same person in the future:

    try {
      $customer = \Stripe\Customer::create([
        "source" => $token // the payment token received from the app
      ]);

      // update the user to include the Stripe customer ID
      DB::table('users')
        ->where('access_token', $access_token)
        ->update([
          'stripe_customer_id' => $customer->id
        ]);

      $payment['customer'] = $customer->id; // assign the ID of the newly created customer to the payment
    } catch (\Exception $e) {
      Log::info("Cannot create Stripe customer for user: " . $user->id);
    }

Lastly, expose the server to the internet using ngrok. If you use Laravel Valet, first you have to change the default top-level domain to be the same as the one we’re using:

    valet domain test

Next, navigate to the root of RNPayBackend and execute valet park. This will register rnpaybackend.test on your local host which you can then expose it using ngrok:

    ngrok http -host-header=rewrite rnpaybackend.test:80

If you used Laravel Homestead, you can log in to your Homestead machine via vagrant ssh and run share rnpay.test. This will use ngrok to expose the server.

Once that’s done, you can update the src/helpers/pay.js file in the app with the HTTPS URL from ngrok.

At this point, the app should be fully functional. Go ahead and run it:

    react-native run-android

Conclusion

In this tutorial, we learned how to use the Tipsi-Stripe library and the Lumen PHP framework to accept payments within a React Native app.

You can find the full source code on this GitHub repo.

Create a todo app React Native for iOS, Android and web

Create a todo app React Native for iOS, Android and web

In this tutorial, I will be describing how to create a todo app with React Native for iOS, Android and web.

You will need Node and Yarn installed on your machine.

The application will be a Todo app but will also make use of Pusher Channels for realtime functionality. You can find a demo of the application below:

In the results of Stack Overflow’s 2019 developer survey, JavaScript happens to be the most popular technology. This is not by mere luck as it has proven we can write applications that can run almost anywhere - from web apps, desktop apps, android apps and iOS apps.

Prerequisites Directory setup

You will need to create a new directory called realtime-todo. In this directory, you will also need to create another one called server. You can make use of the command below to achieve the above:

    $ mkdir realtime-todo
    $ mkdir realtime-todo/server
Building the server

As you already know, we created a server directory, you will need to cd into that directory as that is where the bulk of the work for this section is going to be in. The first thing you need to do is to create a package.json file, you can make use of the following command:

$ touch package.json

In the newly created file, paste the following content:

    // realtime-todo/server/package.json
    {
      "name": "server",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "dependencies": {
        "body-parser": "^1.18.3",
        "cors": "^2.8.5",
        "dotenv": "^7.0.0",
        "express": "^4.16.4",
        "pusher": "^2.2.0"
      },
      "devDependencies": {},
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      }
    }

After which you will need to actually install the dependencies, that can be done with:

$ yarn

Once the above command succeeds, you will need to create an index.js file that will house the actual todo API. You can create the file by running the command below:

$ touch index.js

In the index.js, paste the following contents:

    // realtime-todo/server/index.js

    require('dotenv').config({ path: 'variable.env' });

    const express = require('express');
    const bodyParser = require('body-parser');
    const cors = require('cors');
    const Pusher = require('pusher');

    const pusher = new Pusher({
      appId: process.env.PUSHER_APP_ID,
      key: process.env.PUSHER_APP_KEY,
      secret: process.env.PUSHER_APP_SECRET,
      cluster: process.env.PUSHER_APP_CLUSTER,
      useTLS: true,
    });

    const app = express();

    app.use(cors());
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));

    app.post('/pusher/auth', function(req, res) {
      var socketId = req.body.socket_id;
      var channel = req.body.channel_name;
      var auth = pusher.authenticate(socketId, channel);
      res.send(auth);
    });

    const todos = [];

    app.get('/items', (req, res) => {
      res.status(200).send({ tasks: todos });
    });

    app.post('/items', (req, res) => {
      const title = req.body.title;

      if (title === undefined) {
        res
          .status(400)
          .send({ message: 'Please provide your todo item', status: false });
        return;
      }

      if (title.length <= 5) {
        res.status(400).send({
          message: 'Todo item should be more than 5 characters',
          status: false,
        });
        return;
      }

      const index = todos.findIndex(element => {
        return element.text === title.trim();
      });

      if (index >= 0) {
        res
          .status(400)
          .send({ message: 'TODO item already exists', status: false });
        return;
      }

      const item = {
        text: title.trim(),
        completed: false,
      };

      todos.push(item);

      pusher.trigger('todo', 'items', item);

      res
        .status(200)
        .send({ message: 'TODO item was successfully created', status: true });
    });

    app.post('/items/complete', (req, res) => {
      const idx = req.body.index;

      todos[idx].completed = true;

      pusher.trigger('todo', 'complete', { index: idx });

      res.status(200).send({
        status: true,
      });
    });

    app.set('port', process.env.PORT || 5200);
    const server = app.listen(app.get('port'), () => {
      console.log(`Express running on port ${server.address().port}`);
    });

In the above, we create an API server that has three endpoints:

  • /items : an HTTP GET request to list all available todo items.
  • /items : an HTTP POST request to create a new todo item.
  • /items/complete: used to mark a todo item as done.

Another thing you might have noticed in on Line 3 where we make mention of a file called variable.env. That file does not exists yet, so now is the time to create it. You can do that with the following command:

$ touch variable.env

In the newly created file, paste the following content:

    // realtime-todo/server/variable.env

    PUSHER_APP_ID="PUSHER_APP_ID"
    PUSHER_APP_KEY="PUSHER_APP_KEY"
    PUSHER_APP_SECRET="PUSHER_APP_SECRET"
    PUSHER_APP_CLUSTER="PUSHER_APP_CLUSTER"
    PUSHER_APP_SECURE="1"

Please make sure to replace the placeholders with your original credentials

You can go ahead to run the server to make sure everything is fine. You can do that by running the command:

$ node index.js
Building the client

The client we will build in this section will run on the web. With the help of Expo and React Native, it will also run on Android and iOS. This is made possible via a library called [react-native-web]([https://github.com/necolas/react-native-web)](https://github.com/necolas/react-native-web)).

To get up to speed, we will make use of a starter pack available on GitHub. You will need to navigate to the project root i.e realtime-todo and clone the starter pack project. That can be done with the following command:

    # Clone into the `client` directory
    $ git clone [email protected]:joefazz/react-native-web-starter.git client

You will need to cd into the client directory as all changes to be made will be done there. You will also need to install the dependencies, that can be done by running yarn. As we will be making use of Pusher Channels and at the same time communicate with the server, you will need to run the following command:

$ yarn add axios pusher-js

The next step is to open the file located at src/App.js. You will need to delete all the existing content and replace with the following:

    // realtime-todo/client/src/App.js

    import React, { Component } from 'react';
    import {
      StyleSheet,
      Text,
      View,
      FlatList,
      Button,
      TextInput,
      SafeAreaView,
    } from 'react-native';
    import axios from 'axios';
    import Alert from './Alert';
    import Pusher from 'pusher-js/react-native';

    const APP_KEY = 'PUSHER_APP_KEY';
    const APP_CLUSTER = 'PUSHER_APP_CLUSTER';

    export default class App extends Component {
      state = {
        tasks: [],
        text: '',
        initiator: false,
      };

      changeTextHandler = text => {
        this.setState({ text: text });
      };

      addTask = () => {
        if (this.state.text.length <= 5) {
          Alert('Todo item cannot be less than 5 characters');
          return;
        }

        // The server is the actual source of truth. Notify it of a new entry so it can
        // add it to a database and publish to other available channels.
        axios
          .post('http://localhost:5200/items', { title: this.state.text })
          .then(res => {
            if (res.data.status) {
              this.setState(prevState => {
                const item = {
                  text: prevState.text,
                  completed: false,
                };

                return {
                  tasks: [...prevState.tasks, item],
                  text: '',
                  initiator: true,
                };
              });

              return;
            }

            Alert('Could not add TODO item');
          })
          .catch(err => {
            let msg = err;

            if (err.response) {
              msg = err.response.data.message;
            }

            Alert(msg);
          });
      };

      markComplete = i => {
        // As other devices need to know once an item is marked as done.
        // The server needs to be informed so other available devices can be kept in sync
        axios
          .post('http://localhost:5200/items/complete', { index: i })
          .then(res => {
            if (res.data.status) {
              this.setState(prevState => {
                prevState.tasks[i].completed = true;
                return { tasks: [...prevState.tasks] };
              });
            }
          });
      };

      componentDidMount() {
        // Fetch a list of todo items once the app starts up.
        axios.get('http://localhost:5200/items', {}).then(res => {
          this.setState({
            tasks: res.data.tasks || [],
            text: '',
          });
        });

        const socket = new Pusher(APP_KEY, {
          cluster: APP_CLUSTER,
        });

        const channel = socket.subscribe('todo');

        // Listen to the items channel for new todo entries.
        // The server publishes to this channel whenever a new entry is created.
        channel.bind('items', data => {
          // Since the app is going to be realtime, we don't want the same item to
          // be shown twice. Device A publishes an entry, all other devices including itself
          // receives the entry, so act like a basic filter
          if (!this.state.initiator) {
            this.setState(prevState => {
              return { tasks: [...prevState.tasks, data] };
            });
          } else {
            this.setState({
              initiator: false,
            });
          }
        });

        // This "complete" channel here is for items that were recently marked as done.
        channel.bind('complete', data => {
          if (!this.state.initiator) {
            this.setState(prevState => {
              prevState.tasks[data.index].completed = true;
              return { tasks: [...prevState.tasks] };
            });
          } else {
            this.setState({
              initiator: false,
            });
          }
        });
      }

      render() {
        return (
          // SafeAreaView is meant for the X family of iPhones.
          <SafeAreaView style={{ flex: 1, backgroundColor: '#F5FCFF' }}>
            <View style={[styles.container]}>
              <FlatList
                style={styles.list}
                data={this.state.tasks}
                keyExtractor={(item, index) => index.toString()}
                renderItem={({ item, index }) => (
                  <View>
                    <View style={styles.listItemCont}>
                      <Text
                        style={[
                          styles.listItem,
                          item.completed && { textDecorationLine: 'line-through' },
                        ]}
                      >
                        {item.text}
                      </Text>
                      {!item.completed && (
                        <Button
                          title="✔"
                          onPress={() => this.markComplete(index)}
                        />
                      )}
                    </View>
                    <View style={styles.hr} />
                  </View>
                )}
              />

              <TextInput
                style={styles.textInput}
                onChangeText={this.changeTextHandler}
                onSubmitEditing={this.addTask}
                value={this.state.text}
                placeholder="Add Tasks"
                returnKeyType="done"
                returnKeyLabel="done"
              />
            </View>
          </SafeAreaView>
        );
      }
    }

    const styles = StyleSheet.create({
      container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#F5FCFF',
        paddingTop: 20,
        height: '100%',
      },
      list: {
        width: '100%',
      },
      listItem: {
        paddingTop: 2,
        paddingBottom: 2,
        fontSize: 18,
      },
      hr: {
        height: 1,
        backgroundColor: 'gray',
      },
      listItemCont: {
        flexDirection: 'row',
        alignItems: 'center',
        justifyContent: 'space-between',
      },
      textInput: {
        height: 40,
        paddingRight: 10,
        borderColor: 'gray',
        width: '100%',
      },
    });

Please update Line 17 and 18 to contain your actual credentials.

While the above is pretty straight forward, perhaps the most interesting is the line that reads Alert('Could not add TODO item');. It is easy to think Alert.alert() should be used, while that is true, react-native-web doesn’t include support for the Alert component so we will have to roll out our own. Here is a list of all components react-native-web supports. Building functionality for making alerts on the web isn’t a herculean task. You will need to create a new file called Alert.js in the src directory.

$ touch src/Alert.js

In the newly created file Alert.js, paste the following contents:

    // realtime-todo/client/src/Alert.js

    import { Platform, Alert as NativeAlert } from 'react-native';

    const Alert = msg => {
      if (Platform.OS === 'web') {
        alert(msg);
        return;
      }

      NativeAlert.alert(msg);
    };

    export default Alert;

Simple right ? We just check what platform the code is being executed on and take relevant action.

With that done, you will need to go back to the client directory. This is where you get to run the client. Depending on the platform you want to run the app in, the command to run will be different:

  • Web : yarn web. You will need to visit [http://localhost:3000](http://localhost:3000).
  • Android/iOS : yarn start-expo

If you go with the second option, you will be shown a web page that looks like the following:

You can then click on the links on the left based on your choice.

Remember to leave the server running

If you open the project on the web and on iOS/Android, you will be able to reproduce the demo below:

Conclusion

In this tutorial, I have described how to build an application that runs on Android, iOS and the web with just one codebase. We also integrated Pusher Channels so as to make communication realtime.

As always, you can find the code on GitHub.

Thanks for reading

If you liked this post, share it with all of your programming buddies!

Follow us on Facebook | Twitter

Learn More

The Complete React Native and Redux Course

React Native - The Practical Guide

The complete React Native course ( 2nd edition )

React Native Web Full App Tutorial - Build a Workout App for iOS, Android, and Web

How to build a Chat App with React Native

React Native on Windows