I’ve wanted to get into mobile development for awhile now, but every time I start building something on the side, I deal with the same annoying problems:
After some internet browsing , it seemed like Facebook solved this problem with React Native (a library for making native apps based on React.js), but after trying it I found set up was just as tedious as before. The main problem was still there: I needed to write a bunch of boilerplate Java / C# code.
I was ready to concede building on mobile, but I found something called Expo. Expo has a few libraries and tools that build on the success of React Native in a few meaningful ways:
My most recent creation is an Instagram feed app, which we’re going to make. You can follow along on expo snack (a web interface for playing around with expo). You can also check out the source on github or watch my video tutorial. Let’s get started.
Finished app
From a high level, we can see that the app consists of a list of posts, metadata about the post, and finally the post comments. We need to use the Instagram api to get this information, and then display it in a list format.
Let’s set up an Instagram client app so we can make api requests.
After filling out the information to create a client app, uncheck the box to disable implicit OAuth. For the simplicity of our demo app, we’ll be getting our access token without any server side code.
Now click manage clients and manage on your newly created client. Click security and enter any url in the ‘valid redirect’ field (I did http://www.google.com).
Almost done. To get our access token, we’re making an api call using Instagram’s authentication api. The api is going to redirect us to the specified url, but with our access token as a url parameter. Here’s what you need to paste into your browser:
https://api.instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=token&scope=public_content
Where CLIENT-ID and REDIRECT-URI are the respective values. After approving the apps access to your Instagram account, you should be redirected. Here’s where I grab my access token (please create a unique one for your app).
Nice. We can finally start coding.
Here’s the skeleton for any Expo project:
import React, { Component } from 'react';import { Text, View, StyleSheet } from 'react-native';
export default class App extends Component {render() {return (<View><Text>Hi</Text></View>)}}
const styles = StyleSheet.create({})
The html-style tags are called JSX, a feature of React. We can use it to define our layout, and embed javascript logic inside.
Lets import a few more components from React Native and Expo.
We’re also going to need our access token and somewhere to store data about each post. In React Native, this is done with something called state. Any data the UI will need to access should be stored here. It’s convenient because whenever the state changes, our app is re-rendered.
import React, { Component } from 'react'import {Text,View,StyleSheet,Image,Dimensions,FlatList,} from 'react-native'import { Constants, AppLoading } from 'expo'
access_token = '6626870867.cbba754.83fa37c865314df8be5c52347e3e4987'
state = {loaded: false,data: null,comments: [],}
export default class App extends Component {render() {return (<View style={styles.container}><FlatListdata={this.state.data}renderItem={({ item, index }) => this.createPost(item, index)}keyExtractor={(item) => item.id}/></View>)}}
const styles = StyleSheet.create({container: {flex: 1,alignItems: 'center',paddingTop: Constants.statusBarHeight,backgroundColor: '#ecf0f1',}})
The data prop of FlatList is an array where each item is passed as a parameter to the renderItem function. We’re going to store our Instagram posts in this array.
So, lets use our access_token to get our post data. We use the fetch api to make a GET request to Instagram’s api endpoint. Then we store the values in our apps state.
async fetchFeed () {let response = await fetch('https://api.instagram.com/v1/users/self' +'/media/recent/?access_token=' +this.access_token)let posts = await response.json()let comments = await this.makeCommentsList(posts.data)
this.setState({data: posts.data,comments: comments,loaded: true})}
We have the list of our recent posts, but for each post we need to get the list of comments and associated metadata. This means we’re making a series of networking requests, which could return in the incorrect order. The plan is to handle this by returning an array of Promises that will resolve when the posts comments are retrieved. We also save some load time by not making a request when the post has no comments, and simply showing a View that says ‘No Comments!’
async makeCommentsList(posts) {let postsArray = posts.map(async (post) => {let postId = post.id
if (post.comments.count === 0) {
return (
<View style={styles.comment} key={postId}>
<Text>No Comments!</Text>
</View>
)
} else {
let response = await fetch(
'[https://api.instagram.com/v1/media/'](https://api.instagram.com/v1/media/%27) +
postId +
'/comments?access\_token=' +
this.access\_token
)
let comments = await response.json()
let commentsArray = comments.data
let commentsList = commentsArray.map(commentInfo => {
return (
<View style={styles.comment} key={commentInfo.id}>
<Text style={styles.commentText}>{commentInfo.from.username}</Text>
<Text>{commentInfo.text}</Text>
</View>
)
})
return commentsList
}
})
postsArray = await Promise.all(postsArray)
return postsArray
}
We added some styling to make the comments look nice:
const styles = StyleSheet.create({container: {flex: 1,alignItems: 'center',paddingTop: Constants.statusBarHeight,backgroundColor: '#ecf0f1',},comment: {flexDirection: 'row',padding: 10,paddingLeft: 15,borderBottomWidth: 1,borderColor: '#d8d8d8',},commentText: {paddingRight: 15,fontWeight: 'bold',}})
Now that we have all our comments and post data, we need to make the timeline and populate it with the data we got. The createPost function uses these values to display the post, metadata, and comments.
createPost(postInfo, index) {let imageUri = postInfo.images.standard_resolution.urllet username = postInfo.user.usernamelet numLikes = postInfo.likes.count
return (<View><Image style={styles.image} source={{ uri: imageUri }} /><View style={styles.info}>
<Text style={styles.infoText}>{username}</Text>
<Text style={styles.infoText}>
{numLikes + (numLikes !== 1 ? ' likes' : ' like')}
</Text>
</View>
<View>
{this.state.comments\[index\]}
</View>
</View>
)
}
imageUri, username, and numLikes are all retrieved from the returned JSON from our fetch request to the Instagram api. Example return values can be found on the Instagram developer docs.
Once again, we add some styling to our StyleSheet to make our posts look fancy.
image: {width: Dimensions.get('window').width,height: Dimensions.get('window').width,},info: {flexDirection: 'row',justifyContent: 'space-between',padding: 10,paddingLeft: 15,paddingRight: 15,borderBottomWidth: 1,borderColor: '#d8d8d8',},infoText: {fontSize: 16,fontWeight: 'bold',}
To add some finishing touches, lets fetch our Instagram information when the app starts up and show a loading icon for optimal user experience.
componentDidMount () {this.fetchFeed()}
render () {if (!this.state.loaded) {return (<AppLoading />)}
return (
<View style={styles.container}>
<FlatList
data={this.state.data}
renderItem={({ item, index }) => this.createPost(item, index)}
keyExtractor={(item) => item.id}
/>
</View>
)
}
And just like that, you’re done! A mobile Instagram feed viewer in ~150 lines. You can check out the full, working snack example here.
Since this tutorial was meant to be done without any installations, we haven’t completed the process for making a standalone Android or iOS app. Luckily, you’re just a few steps away.