alonbee / react-native-workshop

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

react-native-workshop

Today we'll be using react-native to build a simple iOS app that allows us to search YouTube (just like short assignment 4, way back when.)

Note: for this workshop, you don't need to clone or fork this repo. Everything we do will be done locally.

** Another important note! If you are of the following people:

  • Windows User
  • Mac User who didn't follow instructions and didn't download XCode
  • Mac User who didn't follow instructions and didn't update XCode

It just takes a while to download/update XCode and we have lots to do today! (* cough * quiz * cough * )

Special Notes

πŸš€ Take special note of this

❄️ This is pretty cool

πŸ“· Take a screenshot!

Set Up:

⚠️ We should already have node installed on our machines, but just in case, let's go way to the beginning:

$ brew install node

πŸš€ And we should install Watchman, too, since react-native depends on it.

$ brew install watchman

Watchman is a file watching service that records when files change, and triggers actions when it detects changes.

πŸš€ Alright, now we're ready to use react-native! We'll want to install the Command Line Interface so we can call react-native commands from the terminal:

$ npm install -g react-native-cli

Great! Now we're ready to create our repo.

$ react-native init VidSearch
$ cd VidSearch
$ react-native run-ios

When the iPhone simulator pops up, you should be seeing a basic template for an iPhone app.

❄️ What just happened? React-Native bundled up everything for us, ran it through XCode, and opened up a simulator, all through the command line and without us ever having to open up XCode or write a single line of Swift code. Neat.

Let's take a sec to talk about how the simulator works. There are two things you can do: command-R will refresh the simulator, which is useful each time you save. It's a little annoying, though, especially since we're used to hot reloading in the browser at this point. If you hit command-D, a menu will pop up. From there, you can click "Enable Hot Reloading". It might take a few seconds to load (don't panic like we did when the percent complete actually starts going down...) but when it's done it'll refresh the page every time we save something in the project.

❄️ Side note: We should appreciate how cool this is. If you've ever tried to program for smartphones before, you know that each time you test, you have to press run in the IDE and then wait for the app to compile. If you're uploading to an actual device instead of a simulator, it takes even longer. React Native is cutting through all the overhead for us so we can do instantaneous reloads without all the wait time.

Installing Dependencies

We're going to need a few dependencies from our trusty friend, the Node Package Manager.

πŸš€ Since we're making calls to the YouTube api, it would help if we made GET calls with axios, so:

$ npm install --save axios

Next, we'll need some specific react-native components that some other open source developers have kindly provided to us. This is fairly common in the react-native community, and it's great to have these pre-styled components at our disposal so we don't have to go through all the trouble of making an input field look nice, for example.

πŸš€ There's one additional componentswe'll be using in the workshop: react-native-search-box, a simple input field made to look like the classic iOS search bar.

$ npm install --save react-native-search-box

Basic Navigation

One of the classic navigation components in iOS is the Tab Bar.

πŸš€ Create a new directory in the top level of the project folder called components. Then create two new files: components/search.js and components/featured.js.

import React, { Component } from 'react';

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

class Search extends Component {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.text}>
          Search component
        </Text>
      </View>
    );
  }
}

module.exports = Search;

πŸš€ And for do the same for your featured component (with whatever refactoring is necessary).

πŸš€ Now let's connect them all together. Navigate to the file called index.ios.js. This is the top-level file for iOS dev in react native. (Side note: there's also an index.android.js file, which makes it fairly easy to convert to Android as well1 There are a few components in this workshop that aren't Android-friendly, so we won't be dealing with it, but it's nice to know it's there.)

πŸš€ Alright, so in index.ios.js, add the following code:

import React, { Component } from 'react';
import Featured from './components/featured';
import Search from './components/search';

import {
  AppRegistry,
  TabBarIOS,
} from 'react-native';

class VidSearch extends Component {

  constructor(props) {
    super(props);
    this.state = {
      selectedTab: 'search'
    };
  }

  render() {
    return (
      <TabBarIOS selectedTab={this.state.selectedTab}
        translucent={false}
        unselectedItemTintColor='#9E9E9E'
        tintColor='#c4302b'
      >
        <TabBarIOS.Item
          selected={this.state.selectedTab === 'search'}
          systemIcon='search'
          onPress={() => {
            this.setState({
              selectedTab: 'search'
            });
          }}>
          <Search />
        </TabBarIOS.Item>
        <TabBarIOS.Item
          selected={this.state.selectedTab === 'featured'}
          systemIcon='featured'
          onPress={() => {
            this.setState({
              selectedTab: 'featured'
            });
          }}>
          <Featured />
        </TabBarIOS.Item>
      </TabBarIOS>
    );
  }
}

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

Alright, now head over to your simulator.

What's going on here? We forgot to add styling! Head back to search.js and add the following styling:

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

πŸš€ Don't forget to add StyleSheet to your imports at the top!

❄️ Let's break down how this styling is working. Notice that we create a constant for our component called styles that contains a dictionary, where the id (in this case container), corresponds to that called by the style prop in the component itself. Take a look at the <View> tag in search.js. It's referencing styles.container, which is what we just declared. Also notice how there's no separate css stylesheet--all styling has to be done inline in React Native. This makes it easier to send through the bridge, which connects our JS code to native code.

πŸš€ Copy the same thing into featured.js. Refresh the simulator again. Now we have a nice looking tab bar at the bottom of the page. Should look something like this:

tab bar

Let's make the featured page look a little less boring. Replace all the code in it with this:

import React, { Component } from 'react';
import ImageView from './imageView';

import {
    StyleSheet,
    View,
    NavigatorIOS,
    Text,
    Image,
    StatusBar,
  } from 'react-native';

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

const Featured = (props) => {

  return (
    <View style={styles.container}>
    <StatusBar
     backgroundColor="blue"
     barStyle="light-content"
   />
    <NavigatorIOS
      style={styles.container}
      translucent={false}
      barTintColor='#c4302b'
      titleTextColor='white'
      initialRoute={{
        title: 'Yay React',
        component: ImageView,
      }}
    />
    </View>
  );
};


module.exports = Featured;

πŸš€ Now create a new file called components/imageView.js. We'll just put this nice react logo in it, so the page isn't so dreadfully boring.

Here's some more code!

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


const ImageView = (props) => {
  return (
    <View>
      <Image
        style={{ width: 400, height: 300 }}
        source={{ uri: 'https://facebook.github.io/react/img/logo_og.png' }}
      />
    </View>
  );
};

module.exports = ImageView;

❄️ Now, instead of some gross text up in the top corner, we have this nice react logo, since we love react so much:

featured tab

Adding Content to the Search Page

So we've got some basic navigation working on the app, but it looks pretty boring. Let's make some cool stuff on the search tab, like how about a nice table view?

Since a table-view is specific to iOS, but react-native is cross-platform, there's no actual table view component. Instead, what we'll need to do is create a list component and instantiate it in our search page.

πŸš€ Create a new file: components/video_list.js. Add some imports:

import React, { Component } from 'react';
import youtubeSearch from '../youtube-api';
import axios from 'axios';
import Search from 'react-native-search-box';

import {
    StyleSheet,
    View,
    Image,
    Text,
    TextInput,
    ListView,
    TouchableHighlight,
  } from 'react-native';

πŸš€ Now that that's there, let's import it into search.js so we can use it.

πŸš€ And lets create a new class component:

import VideoDetail from './video_detail';

// Add styling here

class VideoList extends Component {

  constructor(props) {
    super(props);
    this.state = {
      query: 'dog',
      isLoading: true,
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
    };
  }

  //---------- componentDidMount here! -----------//

  //------------ put fetchData here! -------------//

  renderLoadingView() {
    return (
      <View style={styles.loading}>
        <Text>
          Loading videos...
        </Text>
      </View>
    );
  }

  //Handle your transition to the detail page
  //pass along the clicked video into props to display it!
  showVideoDetail(video) {
    this.props.navigator.push({
      title: video.snippet.title,
      component: VideoDetail,
      passProps: { video },
    });
  }

  renderVideoCell(video) {
    return (
      <TouchableHighlight onPress={() => { this.showVideoDetail(video); }} underlayColor="#dddddd">
        <View>
          <View style={styles.container}>
            //----- TableView Content should go here -----//
          </View>
          <View style={styles.separator} />
        </View>
      </TouchableHighlight>
    );
  }

  render() {
    if (this.state.isLoading) {
      return this.renderLoadingView();
    }
    return (
      <View style={{ marginBottom: 150 }}>
        <Search
          backgroundColor='#c4302b'
          showsCancelButton={false}
          textFieldBackgroundColor='#c4302b'
          onChangeText={(query) => {
            this.setState({ query });
            // Call fetchData here!
            this.fetchData();
          }
          }
        />

        //This is equivalent to TableView in iOS.
        //You need to define the location of your data in dataSource
        //and define cell structure with renderRow
        <ListView
          dataSource={this.state.dataSource}
          renderRow={this.renderVideoCell.bind(this)}
          style={styles.listView}
        />
      </View>
    );
  }
}

module.exports = VideoList;

πŸš€ And we should add in some styles too. Let's make it a little more interesting this time:

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'flex-start',
    alignItems: 'flex-start',
    backgroundColor: 'white',
    padding: 10,
  },
  thumbnail: {
    width: 80,
    height: 80,
    marginRight: 10,
  },
  rightContainer: {
    flex: 1,
  },
  title: {
    fontSize: 16,
    fontWeight: 'bold',
    marginBottom: 3,
  },
  subtitle: {
    fontSize: 12,
  },
  separator: {
    height: 1,
    backgroundColor: '#dddddd',
  },
  listView: {
    backgroundColor: 'white',
  },
});

❄️ A few things to note here:

  • We can really see how the styles are represented as dictionaries, with the ids being similar to css classes
  • Property names need not go in quotes, but if the property itself is a string, it should (like in backgroundColor: 'white')
  • Flex is dealt with in kind of a strange way. Instead of a display property, these stylesheets default to flex when you simply specify flex: followed by some integer. This integer is a lot like the flex-grow property that we're familiar with.
  • Finally, notice that all our integer values are simply integers! There's no px, pt, em, etc. Only numbers.

πŸš€ Take a look at the simulator. We've now got some text indicating that the videos are loading. This is the default text we've provided if the API call hasn't returned videos yet. Since we haven't made an API call yet, that definitely makes sense.

πŸ“· Take a screenshot of the search tab in the simulator at this point. You'll upload this later.

πŸš€ Now, let's populate the table view! Add in the following lines to instantiate an image component containing the video thumbnail and some text with the video's title and description.

<Image
  source={{ uri: video.snippet.thumbnails.default.url }}
  style={styles.thumbnail}
/>
<View style={styles.rightContainer}>
  <Text style={styles.title}>{video.snippet.title}</Text>
  <Text style={styles.subtitle}>{video.snippet.description}</Text>
</View>

πŸš€ Hmm...simulator says still just loading videos. That's because we need to actually gather our data from Youtube. Let's add in the fetchData method to make our API call.

fetchData() {
  youtubeSearch(this.state.query)
     .then((responseData) => {
       this.setState({
         dataSource: this.state.dataSource.cloneWithRows(responseData),
         isLoading: false,
       });
     })
     .done();
}

Where should we call this from? It would be nice if we could get the data from YouTube as soon as we get to the page. Can you recall from your React mastery which life cycle component is the ideal place to call it? You guessed it.

πŸš€ Create a function componentDidMount in video_list.js. Inside it, make a call to fetchData.

❄️ Now when the page loads, we'll call fetchData to populate our list view.

πŸš€ Alright, let's update the search.js file to have a table view that lists all our videos. Replace the return statement in the render function with the following:

<View style={styles.container}>
  <StatusBar
    backgroundColor="blue"
    barStyle="light-content"
  />
  <NavigatorIOS
    style={styles.container}
    translucent={false}
    barTintColor='#c4302b'
    titleTextColor='white'
    tintColor='white'
    initialRoute={{
      title: 'Featured Videos',
      component: VideoList,
    }}
  />
</View>

Ah darn, one other thing. We need to actually have a reference to the API, right? This next part should (hopefully) look super familiar.

πŸš€ Create a new file at the top level of your project, youtube-api.js.

Sound familiar? We did this in short assignment 4, and we will be using the exact same api for this react-native app! That's coooool.

Go ahead and find that file and copy it here. We need to do this because we need your individual api key, which you already made in sa4.

πŸš€ What's this videoDetail thing? We'll also need to create that. Make a new file called compnents/video_detail.js and paste in this code:

import React, { Component } from 'react';

import {
    StyleSheet,
    Text,
    View,
    Image,
    WebView,
  } from 'react-native';

const styles = StyleSheet.create({
  container: {
    alignItems: 'center',
  },
  image: {
    width: 107,
    height: 165,
    padding: 10,
  },
  description: {
    padding: 10,
    fontSize: 15,
    color: '#656565',
  },
});

class VideoDetail extends Component {
  render() {
    const video = this.props.video;
    const description = video.snippet.description || '';
    const vidId = video.id.videoId;
    return (
      <WebView
          style={styles.frame}
          source={{uri: `https://www.youtube.com/watch?v=${vidId}`}}
          renderLoading={this.renderLoading}
          renderError={this.renderError}
          automaticallyAdjustContentInsets={false}
      />
    );
  }
}

module.exports = VideoDetail;

❄️ This is a little different from what we've been doing. The WebView component is a sort of hybrid component that's actually just rendering a webpage. The source prop holds a uri that's called as if in a browser and then displayed in our application. Notice how it looks just like watching youtube on a mobile device. Pretty cool that we can do this within our application alongside native components, huh?

Here's what the app should be looking like now: finished app

πŸš€ Now that the app is complete, we're using all the styling we pasted in awhile ago. Now it's your turn: play around with the styling in video_list.js. If you haven't enabled hot-reloading yet, do that, it'll make it easy to see all your styling changes.

πŸ“· Make the styling uniquely your own. Then, search for some unique searchterm and take a screenshot. You'll upload this to your repo to turn in.

And We Are Done!

Look at you! You spend eight weeks in full-stack web dev, but little did you know it was actually smartphone programming in disguise! Here's what we accomplished today:

  • Learned how to set up a new iOS project in react native (from scratch!)
  • Implement Tab Bar navigation (without Swift code)
  • Learn how to use the react-native simulator
  • Made a table view on an iPhone in JavaScript
  • Make an API call to YouTube using axios
  • Evaded a quiz for an extra hour!

Submission

To submit, create a new github repo and push your code up to it. In the README file, include the two screenshots we asked you to provide. On canvas, submit the URL to your repo. If you had a partner, submit their name too, so we can count the assignment for both of you.

Resources

Really like React-Native? Here are some more resources for you to look at and play around with:

About