(Build real native application with javascript, and Run on Expo)
- Create a New Project in Expo XDE
- FlexBox Practice
- Say Hello to a New Component:
CoinView
- Good Bye StatusBar (Props)
- Top Bar
- Props with TopBar Components
- Brand New CoinDetail Component
- Push Dummies Into The CoinDetail Component
- It's Real Data
- Upgrade TopBar
- ScrollView. Another Component
- Oh, Beauty
- React Native Basics: Component, Props, State, JSX...
- ES2015(ES6):
import
,from
,extends
,=>
- Node.js: React Native uses
Node.js
to build code - Expo:
Our tools enable developers to build and share truly native apps that work across both iOS and Android. Everything is open source, free and uses React Native. Download our tools
-
iOS Simulator: XCode(with Mac). Strongly recommend it for fast build. You can to skip it if you build your project on you device only.(not recommend for Kosscon 2017)
-
Android Simulator: Genymotion
On Android we recommend the Genymotion emulator over the standard emulator — we have found it to be more feature complete, faster and easier to use. Download Genymotion (free version) and follow the Genymotion installation guide. Once you’ve installed Genymotion, create a virtual device - we recommend a Nexus 5, the Android version is up to you. Start up the virtual device when it’s ready. If you run into any issues follow our Genymotion guide.
- Create your project and open on Expo.
- Run your project on the simulator(Device Button on top-right ) or
Expo App
on your device - Open development menus(Android:
cmd+m
, iOS:ctrl+cmd+z
orcmd+d
) and toggleHot/Live reloading
on Expo App - Edit your text and feel the magic
- commnets may do not work on JSX area.
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
export default class App extends React.Component {
render() {
return ( // JSX Area
<View style={styles.container}>
<Text>Open up App.js to start working on your app</Text> // Edit here
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
- Turn on Hot reloading and edit the
Text
component in App.js
- Change your host in XDE to
LAN
orlocalhost
for faster
If you are using LAN, make sure your device is on the same wifi network as your development machine. This may not work on some public networks. localhost will not work for iOS unless you are in the simulator, and it only work on Android if your device is connected to your machine via usb.
- Development menus
- Android Simulator:
cmd+m
- iOS Simulator:
ctrl+cmd+z
orcmd+d
- Genymotion(Android):
Menu
orcmd+m
- Android Simulator:
- Change your applicaiton's layouts using Flexbox with
StyleSheet
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<View style={[styles.box, {backgroundColor: 'red', flex:1}]}></View> { /* Delete flex */ }
<View style={[styles.box, {backgroundColor: 'violet', flex:2}]}></View> { /* Delete flex */ }
<View style={[styles.box, {backgroundColor: 'pink', flex:3}]}></View> { /* Delete flex */ }
<Text>Open up App.js to start working on your app!!!</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
// flexDirection: 'row',
backgroundColor: '#fff',
alignItems: 'center', // edit here
justifyContent: 'space-around', // edit here using `center, space-around`
},
box: {
backgroundColor: 'blue',
width: 50,
height: 50,
}
});
- Change each color of
View
components - Change styles as
backgroundColor
,flex
,alignItems
andjustifyContent
- Make a new file: 'components/CoinView.js'
- Write a new component as below
import React from 'react'
import { StyleSheet, Text, View } from 'react-native';
class CoinView extends React.Component {
render () {
return (
<View style={styles.container}>
<Text>New View </Text>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
// width: '50%',
// flex: 1,
flexDirection: 'column', // row
backgroundColor: 'skyblue',
// alignItems: 'center',
// justifyContent: 'space-around', // center, space-around
},
});
export default CoinView;
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import CoinView from './components/CoinView'; // Call your new friend
export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<View style={[styles.box, {backgroundColor: 'red'}]}></View>
<View style={[styles.box, {backgroundColor: 'green'}]}></View>
<View style={[styles.box, {backgroundColor: 'blue'}]}></View>
<CoinView></CoinView>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
// flexDirection: 'row', // column
backgroundColor: 'yellow',
// alignItems: 'center',
// justifyContent: 'space-between', // center, space-around
},
box: {
backgroundColor: 'blue',
width: 50,
height: 50,
}
});
- Change positions with
CoinView
and other components - Feel the flex
- You may understand about component and styles with flexbox
- Hide status bar through
StatusBar
component
...
import { StyleSheet, Text, View, StatusBar } from 'react-native'; // Use StatusBar component
...
render() {
return (
<View style={styles.container}>
<StatusBar
hidden={true}
backgroundColor="blue"
barStyle="light-content"
/>
<CoinView></CoinView>
</View>
);
}
StatusBar
is the component to control the app status bar. link
Most components can be customized with various parameters called props
(properties).
And hidden
, backgroundColor
and barStyle
are StatusBar
component's props.
- Make another component called
TopBar
- You will know how to make components and use
import React from 'react'
import { StyleSheet, Text, View } from 'react-native';
class TopBar extends React.Component {
render () {
return (
<View style={styles.container}>
<Text>Left</Text>
<Text>TopBar</Text>
<Text>Right</Text>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
width: '100%',
height: 52,
flexDirection: 'row', // row
backgroundColor: 'yellow',
alignItems: 'center',
justifyContent: 'space-between', // center, space-around
},
});
export default TopBar;
...
import TopBar from './components/TopBar';
export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<StatusBar
hidden={true}
backgroundColor="blue"
barStyle="light-content"
/>
<TopBar></TopBar>
<CoinView></CoinView>
</View>
);
}
}
...
- Why did
TopBar
use onlyheight
andCoinView
useflexbox
in the style prop?
class CoinView extends React.Component {
render () {
return (
- <View style={styles.container}>
- <Text>New View </Text>
+ <View style={this.props.style}> { /* Ready to get style from a parent component */ }
+ <Text>코인뷰가 나올것입니다.</Text>
</View>
)
}
return (
<View style={styles.container}>
<Text>Left</Text>
- <Text>TopBar</Text>
+ <Text style={{fontSize: 20}}>{this.props.title}</Text> // Ready to apply title from a parent component
<Text>Right</Text>
</View>
)
backgroundColor="blue"
barStyle="light-content"
/>
- <TopBar></TopBar>
- <CoinView></CoinView>
+ <TopBar title="코인 시세"/>
+ <CoinView style={styles.coinView} />
</View>
);
}
...
const styles = StyleSheet.create({
alignItems: 'center',
justifyContent: 'space-between', // center, space-around
},
- box: {
- backgroundColor: 'blue',
- width: 50,
- height: 50,
+ coinView: {
+ width: '100%',
+ flex: 1,
+ flexDirection: 'column', // row
+ backgroundColor: 'skyblue',
+ alignItems: 'center',
+ justifyContent: 'space-around', // center, space-around
}
});
- Add compoenents/CoinDetail.js file
import React from 'react'
import { StyleSheet, Text, View, Image } from 'react-native';
class CoinDetail extends React.Component {
render () {
let date = new Date();
let now = date.toLocaleString()
return (
<View style={styles.container}>
<Image
style={{width: 50, height: 50}}
source={{uri: 'https://bitcoin.org/img/icons/opengraph.png'}}
/>
<Text style={[styles.text, {flex: 1}]}>{'#' + (this.props.rank || 'Rank')}</Text>
<Text style={[styles.text, {flex: 1}]}>{this.props.name || 'Name'}</Text>
<Text style={[styles.text, {flex: 1}]}>{'Price: ' + (this.props.price || 0)}</Text>
<Text style={[styles.text, {flex: 1}]}>{'Volume: ' + (this.props.volumn || 0)}</Text>
<Text style={[styles.text, {flex: 1}]}>{'Updated: ' + (this.props.time || now)}</Text>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
width: '100%',
height: 80,
flexDirection: 'row', // row
backgroundColor: 'blue',
alignItems: 'center',
justifyContent: 'space-around', // center, space-around
marginTop: 5,
marginBottom: 5,
},
text: {
color: 'white',
}
});
export default CoinDetail;
import React from 'react'
import { StyleSheet, Text, View } from 'react-native';
+import CoinDetail from './CoinDetail';
class CoinView extends React.Component {
render () {
return (
<View style={this.props.style}>
- <Text>코인뷰가 나올것입니다.</Text>
+ <CoinDetail></CoinDetail>
+ <CoinDetail></CoinDetail>
+ <CoinDetail></CoinDetail>
+ <CoinDetail></CoinDetail>
</View>
)
}
}
-
-const styles = StyleSheet.create({
- container: {
- width: '100%',
- flex: 1,
- flexDirection: 'column', // row
- backgroundColor: 'skyblue',
- alignItems: 'center',
- justifyContent: 'space-around', // center, space-around
- },
-});
-
Example Data
const sampleData = [
{
"id": "bitcoin",
"name": "Bitcoin",
"symbol": "BTC",
"rank": "1",
"price_usd": "6195.6",
"price_btc": "1.0",
"24h_volume_usd": "8119580000.0",
"market_cap_usd": "103323711420",
"available_supply": "16676950.0",
"total_supply": "16676950.0",
"max_supply": "21000000.0",
"percent_change_1h": "-1.8",
"percent_change_24h": "4.19",
"percent_change_7d": "-15.65",
"last_updated": "1510556652"
},
{
"id": "ethereum",
"name": "Ethereum",
"symbol": "ETH",
"rank": "2",
"price_usd": "310.13",
"price_btc": "0.0493027",
"24h_volume_usd": "1636680000.0",
"market_cap_usd": "29678006174.0",
"available_supply": "95695373.0",
"total_supply": "95695373.0",
"max_supply": null,
"percent_change_1h": "-0.89",
"percent_change_24h": "1.81",
"percent_change_7d": "4.39",
"last_updated": "1510556649"
},
];
Apply data
render () {
let detailCells = sampleData.map( (data, index) => {
const {rank, name, price_usd, market_cap_usd, time} = data; // Destructuring
return (
<CoinDetail
key={index}
rank={rank}
name={name}
price={price_usd}
volumn={market_cap_usd}
/>
);
});
/** Same expression with above **/
// let detailCells = [];
//
// for (var i = 0; i < sampleData.length; i++) {
// let data = sampleData[i];
// let coinDetail = (
// <CoinDetail
// key={data.index}
// rank={data.rank}
// name={data.name}
// price={data.price_usd}
// volumn={data.market_cap_usd}
// />
// )
// detailCells.push(coinDetail);
// }
return (
<View style={this.props.style}>
+ {detailCells}
</View>
)
}
class CoinView extends React.Component {
constructor(props) {
super(props);
this.state = {
coinDatas: [],
isLoaded: false,
};
// Toggle the state every second
}
componentDidMount() { // After component loaded
this._getCoinDatas(10);
setInterval(() => {
this._getCoinDatas(10);
console.log('toggled!');
}, 10000);
}
_getCoinDatas(limit) {
this.setState({
isLoaded: false,
});
fetch(
`https://api.coinmarketcap.com/v1/ticker/?limit=${limit}`
)
.then(response => response.json())
.then(data => {
this.setState({
coinDatas: data,
isLoaded: true,
});
});
}
render () {
let detailCells = this.state.coinDatas.map( (data, index) => {
const {rank, name, price_usd, market_cap_usd, last_updated} = data; // Destructuring
return (
<CoinDetail
key={index}
rank={rank}
name={name}
price={price_usd}
volumn={market_cap_usd}
/>
);
});
/** Same expression with above **/
// let detailCells = [];
//
// for (var i = 0; i < this.state.coinDatas.length; i++) {
// let data = this.state.coinDatas[i];
// let coinDetail = (
// <CoinDetail
// key={data.index}
// rank={data.rank}
// name={data.name}
// price={data.price_usd}
// volumn={data.market_cap_usd}
// />
// )
// detailCells.push(coinDetail);
// }
return (
<View style={this.props.style}>
{detailCells}
</View>
)
}
}
export default CoinView;
Change raw time to date format
<Text style={[styles.text, {flex: 1}]}>{this.props.name || 'Name'}</Text>
<Text style={[styles.text, {flex: 1}]}>{'Price: ' + (this.props.price || 0)}</Text>
<Text style={[styles.text, {flex: 1}]}>{'Volume: ' + (this.props.volumn || 0)}</Text>
- <Text style={[styles.text, {flex: 1}]}>{'Updated: ' + (this.props.time || now)}</Text>
+ <Text style={[styles.text, {flex: 1}]}>{'Updated: ' + (Date(this.props.time) || now)}</Text> //Date
</View>
)
}
Communication between parent and child components
- Add refresh date information on
TopBar
- Sequence of
refreshDate
's data flow:CoinView.js
->App.js
->TopBar.js
_getCoinDatas(limit) {
this.setState({
isLoaded: false,
});
fetch(
`https://api.coinmarketcap.com/v1/ticker/?limit=${limit}`
)
.then(response => response.json())
.then(data => {
let date = new Date();
let now = date.toLocaleString()
if (this.props.refreshDate != null) {
this.props.refreshDate(now); // Run func type props
}
this.setState({
coinDatas: data,
isLoaded: true,
});
});
}
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
refreshDate: '-',
};
}
_setRefreshDate(date) { // Called from CoinView's prop
console.log('Updated: '+ date);
this.setState({
refreshDate: date,
});
}
render() {
return (
<View style={styles.container}>
<StatusBar
hidden={true}
backgroundColor="blue"
barStyle="light-content"
/>
<TopBar title="코인 시세" refreshDate={this.state.refreshDate} />
<CoinView
refreshDate={(date) => this._setRefreshDate(date)} {/* // function type prop */}
style={styles.coinView} />
</View>
);
}
}
return (
<View style={styles.container}>
<Text>Left</Text>
<View>
<Text style={{fontSize: 20}}>{this.props.title}</Text>
<Text style={{fontSize: 10}}>{this.props.refreshDate || ','}</Text>
</View>
<Text>Right</Text>
</View>
)
Super easy!
- Replace
View
withScrollView
- Remove
justifyContent
andalignItems
from CoinView.js's style. (ScrollView dose not have fixed size).
...
import { StyleSheet, Text, View, ScrollView } from 'react-native';
...
return (
<ScrollView style={this.props.style}>
{detailCells}
</ScrollView>
)
...
It is your turn.
- Change the style better
Let's make look and feel for your application using icon of coins.
Make function to get icon uri from name of coin.
/**
Icons from: https://github.com/cjdowner/cryptocurrency-icons/tree/master/32%402x/icon
*/
export function getCoinIconUri(coinName) {
switch (coinName) {
case 'Bitcoin':
return 'https://github.com/cjdowner/cryptocurrency-icons/blob/master/32@2x/icon/[email protected]?raw=true';
case 'Ethereum':
return 'https://github.com/cjdowner/cryptocurrency-icons/blob/master/32@2x/icon/[email protected]?raw=true';
case 'Ripple':
return 'https://github.com/cjdowner/cryptocurrency-icons/blob/master/32@2x/icon/[email protected]?raw=true';
case 'Bitcoin Cash':
return 'https://github.com/cjdowner/cryptocurrency-icons/blob/master/32@2x/icon/[email protected]?raw=true';
case 'Litecoin':
return 'https://github.com/cjdowner/cryptocurrency-icons/blob/master/32@2x/icon/[email protected]?raw=true';
default:
return 'https://assets-cdn.github.com/images/modules/logos_page/GitHub-Mark.png';
}
}
Ready to get iconUri from props.
...
<Image
style={{width: 50, height: 50, marginRight: 5, marginLeft: 5}}
source={{uri: this.props.iconUri}}
/>
...
Add iconUri={getCoinIconUri(name)}
for the CoinDetail
child component.
...
import { getCoinIconUri } from '../libs/Constants';
...
render () {
let detailCells = this.state.coinDatas.map( (data, index) => {
const {rank, name, price_usd, market_cap_usd, last_updated} = data; // Destructuring
return (
<CoinDetail
key={index}
rank={rank}
name={name}
price={price_usd}
volumn={market_cap_usd}
iconUri={getCoinIconUri(name)}
/>
);
});
return (
<ScrollView style={this.props.style}>
{detailCells}
</ScrollView>
)
}
Refresh and check your icons!
- Change each icon of coins
- Apply Refresh Button on
TopBar
- Show more
Detail
when clicked each rows - Use
ListView
instead of aCoinDetail
component - Make release version for application store from
Expo
or export fromExpo