Building An Image Browsing App With Realm, Imgur API, and React Native

Nader Dabit
10 min readApr 7, 2016

Realm allows you to write to your app’s model layer in a safe, persisted, and fast way. Realm has a custom React Native api and fantastic documentation that can get you up and running quickly.

This app will work cross platform, it is up to you whether you want to build for iOS or Android.

The final application is available here.

This tutorial uses version 0.22.2 of React Native. If you are on a newer version there may be some inconsistencies. I’ll try to update this with the newest version as soon as possible!

We will use Realm along with Imgur’s free api to build an image browser. This app will allow you to add and remove categories of interest, and click on the categories to view these images (which we will persist to the device storage using Realm). Here is what the app will look like once we are done:

To get started, let’s first create a new React Native project.

To get this going, you need to have react-native cli installed on your machine (skip this if you already have React Native cli installed)

npm install -g react-native-cli

// If you have any issues getting React Native going, check out the getting started guide here.

Next, initiate a new project:

react-native init RealmImgur

After this is created, cd into the directory.

Before we go any further, we need to install rnpm (react-native package manager), which will link native dependencies (in this case, Realm) to React Native.

npm i -g rnpm

Now let’s install lodash and Realm and save them as dependencies:

npm i --save realm lodash 

Now that realm is installed, we need to run rnpm link to link our project to the realm native module.

rnpm link realm

Now that these are installed, we need to create an imgur api key. To do this, go to https://api.imgur.com/ and click sign up if you do not have an account, or sign in if you do already have an account:

Next, click on ‘register an application’ on the left menu under general information:

Click on the blue ‘register an application’

  1. Enter a name for your application
  2. Choose ‘anonymous usage without user authorization’.
  3. Enter some url for your callback, this does not matter for us because we are not using any authorization, but they do not let you leave it blank.
  4. Enter your email
  5. Fill out captcha
  6. Submit

You should come to a screen where you can view your client ID and Client secret. You should also receive and email with this info. The only thing we need is the client ID, so save this somewhere so we can reference it later.

We have everything set up now to start building our app.

Create a new folder called app, and in it create three new files: App.js, api.js, and ViewImages.js. App will be our main application view, ViewImages will be our image browser view, and api will hold our api call that we will be using to interact with Imgur. Our folder structure should now look like this:

Open index.ios.js if you are writing for iOS, or index.android.js if you are writing for Android.

Updating index file

Delete what is there now and instead paste in the following:

// index.ios.js or index.android.jsimport React, {
AppRegistry,
Component,
Navigator
} from 'react-native'
import App from './app/App'
import ViewImages from './app/ViewImages'
class RealmImgur extends Component {renderScene (route, navigator) {
if (route.name === 'App') {
return <App navigator={navigator} {...route.passProps} />
}
if (route.name === 'ViewImages') {
return <ViewImages navigator={navigator} {...route.passProps} />
}
}
configureScene (route) {
return Navigator.SceneConfigs.FloatFromBottom
}
render () {
return (
<Navigator
configureScene={this.configureScene.bind(this)}
style={{ flex: 1, backgroundColor: 'white' }}
initialRoute={{ name: 'App' }}
renderScene={this.renderScene.bind(this)} />
)
}
}
AppRegistry.registerComponent('RealmImgur', () => RealmImgur)b
  1. We import AppRegistry, Component, and Navigator from React Native
  2. We our two new components we have yet to write, App.js and ViewImages.js
  3. We instantiate our RealmImgur class
  4. renderScene — this is a Navigator method. It will check the name that is passed to the route, and if it is App it will return the App component. If it is ViewImages, it will return the ViewImages component. We have passed in the navigator as props, and …route.passProps so we can pass properties to the View from our Navigator.push function
  5. configureScene — this is also a Navigator method. It will give us a popup from the bottom as opposed to the default floatFromRight on transition.
  6. In the render function, we are returning an instance of Navigator and passing in an initalRoute of App, along with some basic styling. We also attach our two navigator methods (configureScene and renderScene)

The final code for index.ios.js / index.android.js is here.

Creating App.js

Now that this is set up, let’s go into our App component and set it up:

Before we create our App class, let’s bring in everything we will need from React Native, realm, and lodash, as well as set up our basic Realm schema:

import React, {
View,
Text,
Component,
TextInput,
StyleSheet,
TouchableHighlight,
ScrollView
} from 'react-native'
import Realm from 'realm'
import _ from 'lodash'
let realm = new Realm({
schema: [{name: 'Categories', properties: {name: 'string'}}]
})
let favs = realm.objects('Categories')

Before we go any further, let’s look at the basics of interacting with Realm.

From the docs:

let realm = new Realm({
schema: [{name: 'Dog', properties: {name: 'string'}}]
})

realm.write(() => {
realm.create('Dog', {name: 'Rex'});
})

Now we can access anything written to this Realm like this:

realm.objects('Dog')

To learn more, check out this getting started section on their site.

We will be doing basic things like this:

// Create Realm schema
let realm = new Realm({
schema: [{name: 'Categories', properties: {name: 'string'}}]
})
// query Realm for all Categories
let favs = realm.objects(‘Categories’)
// Add item to Realm
_addItem () {
if (this.state.input === '') return
realm.write(() => {
realm.create('Categories', { name: this.state.input })
})
this.setState({ input: '' })
}
// Delete item from Realm
_deleteItem (name) {
let itemToDelete = favs.filtered('name = $0', name)
realm.write(() => {
realm.delete(itemToDelete)
})
this.forceUpdate()
}

Now that we have a brief understanding of how we can interact with Realm, let’s create our App component:

class App extends Component {  constructor () {
super()
this.state = {
input: ''
}
}
_closeModal () {
this.props.navigator.pop()
}
_updateInput (input) {
this.setState({ input })
}
_addItem () {
if (this.state.input === '') return
realm.write(() => {
realm.create('Categories', { name: this.state.input })
})
this.setState({ input: '' })
}
_deleteItem (name) {
let itemToDelete = favs.filtered('name = $0', name)
realm.write(() => {
realm.delete(itemToDelete)
})
this.forceUpdate()
}
_viewImages (category) {
this.props.navigator.push({
name: 'ViewImages',
passProps: {
closeModal: this._closeModal,
category
}
})
}
render () {
let favorites = _.map(favs, (f, i) => {
return (
<View key={i} style={style.favoriteButtonContainer}>
<TouchableHighlight
onPress={() => this._viewImages(f.name)}
underlayColor='transparent'
style={style.favorite}>
<Text style={style.favoriteText}>{f.name}</Text>
</TouchableHighlight>
<TouchableHighlight
onPress={() => this._deleteItem(f.name)}
underlayColor='transparent'
style={style.deleteButton}>
<Text style={style.deleteText}>&times;</Text>
</TouchableHighlight>
</View>)
})
return (
<View style={style.container}>
<View style={style.headingContainer}>
<Text style={style.heading}>
Welcome to Realm Imgur Viewer
</Text>
</View>
<ScrollView style={style.mainContainer}>
<TextInput
value={this.state.input}
onChangeText={(text) => this._updateInput(text)}
style={style.input}
placeholder='What Do You Like?' />
<View style={style.buttonContainer}>
<TouchableHighlight
underlayColor='#3f62aa'
style={[ style.button ]}
onPress={() => this._addItem()}>
<Text style={style.buttonText}>Add Item</Text>
</TouchableHighlight>
</View>
<View style={style.favContainer}>
<Text style={style.favorites}>FAVORITES</Text>
{favorites}
</View>
</ScrollView>
</View>
)
}
}

There’s a lot going on here, so let’s walk through all of this. (though we will not be going into any details about styling. The code for the styling is in the link below number 8).

  1. In our constructor, we set input as an empty string.
  2. _closeModal is a function that we will pass down as a prop to our ViewImages page to close the modal. pop() is a navigator method that transitions back and unmount the current scene. (read more here or here)
  3. _updateInput is a function that will set the state of our input variable so we can use it to create a new item in our Realm database
  4. _addItem checks to see if we have anything other than an empty string in our input state, and if so it writes to the Realm database and creates a new item with the value of this.state.input. We then reset the state of input to be empty so our TextInput clears out.
  5. _deleteItem filters through our list of item and does a check based on the item name. It then deletes the item from Realm database using the realm.delete function. We forceUpdate to reset the state of our app.
  6. _viewImages takes us to our ViewImages screen. We pass along the _closeModal function as well as the category that is passed into the function. These two items (closeModal, category) will then be available as props in our ViewImage component.
  7. In render, we map over our favs items and return a View with our favs mapped into an array stored in our favorites variable, as well as two buttons: one to view the images for that item name and the other to delete the item.

The final code along with the styling for App.js is here.

Creating the api

Next, let’s create the api call we will be using to interact with Imgur. The endpoint we will be hitting is the gallery tag endpoint. This endpoint takes a gallery tag name with some optional parameters that we will not be using, such as sort and page number (If you would like to extend this app, you can add more pages by using these parameters):

https://api.imgur.com/3/gallery/t/{t_name}

We will be using this endpoint and supplying custom tag names to get custom galleries. For example:

https://api.imgur.com/3/gallery/t/cats

Open app/api.js and type in the following code (be sure to add your own personal api key):

const rootUrl = 'https://api.imgur.com/3/gallery/t/'
const apiKey = 'yourapikeyhere'
module.exports = {
get (url) {
return fetch(rootUrl + url, {
headers: {
'Authorization': 'Client-ID ' + apiKey
}
})
.then((response) => {
return response.json()
})
}
}

For reference, the final code to this file is here.

The last step is to set up the ViewImages.js:

import React, {
View,
Text,
Component,
StyleSheet,
TouchableHighlight,
ScrollView,
Image,
Dimensions,
ListView
} from ‘react-native’
let windowWidth = Dimensions.get(‘window’).width
import API from ‘./api’
let ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2})
class ViewImages extends Component {
constructor (props) {
super(props)
this.state = {
dataSource: ds,
loading: true,
images: []
}
}
componentDidMount () {
API.get(this.props.category)
.then((response) => {
this.setState({
dataSource: ds.cloneWithRows(response.data.items),
loading: false
})
}, (error) => {
console.log('error: ', error)
})
}
renderRow (rowData) {
if (rowData.link.match(/\.(jpg|png|gif)/g)) {
return (
<View>
<Image
source={{ uri: rowData.link }}
style={{width: windowWidth, height: windowWidth}} />
</View>)
} else {
return null
}
}
render () {
let { loading, images } = this.state
if (loading) {
images = (
<View style={style.loadingContainer}>
<Text style={style.loading}>Loading images…</Text>
</View>)
}
if (!loading) {
images = <ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow.bind(this)} />
}
return (
<View style={{flex: 1}}>
<TouchableHighlight
underlayColor=’transparent’
onPress={this.props.closeModal.bind(this)}
style={style.closeButton}>
<Text style={style.closeButtonText}>CLOSE</Text>
</TouchableHighlight>
<ScrollView style={{flex: 1}}>
{images}
</ScrollView>
</View>
)
}
}
  1. import everything we will need from React Native
  2. We use Dimensions to calculate the window width. We will use this to set the style of our images
  3. import our API
  4. Create a new ListView datasource and store it in a variable for later.
  5. Create our class, and set our initial state in the constructor. We set loading to true, images to an array, and dataSource to our instance of ListView.Datasource we created earlier.
  6. In componentDidMount, we use our api to fetch the images for the api, and we pass this.props.category to the function. This.props.category will be passed down as a prop. We then set the dataSource array to response.data.items, and set loading to false.
  7. In renderRow, we check the image link to see if it is either a gif, jpg, or png file, if it is not we do not return anything. If it is one of these types of image, we return an Image and set the source as the link property (returned from imgur) of rowData.
  8. In our renderRow function, we destructure loading and images from our state. If loading is true, images is assigned to a loading message. If loading is false, images is assigned to our ListView.
  9. Finally, we return our images component wrapped in some styling.

The final code for ViewImages.js along with the styling is here.

The final code for the entire app, reflecting where we are now, is here.

Now, we should be able to run the app add items, and then view them:

My Name is Nader Dabit . I am a developer at School Status where we help educators make smart instructional decisions by providing all their data in one place. Check us out @schoolstatusapp.

If you like React and React Native, checkout out our podcast — React Native Radio on Devchat.tv

If you enjoyed this article, please recommend and share it! Thanks for your time

--

--

Nader Dabit

Full Stack Product Engineer, Author, Teacher, Director of Developer Relations at Avara