loading...
Ably Realtime

Building a live flight tracking app in React Native

codeekage profile image Abraham Jr. Agiri ・7 min read

Wouldn't it be cool to have a way to watch flights move across the world; essentially see the flight's live location change in realtime? Of course, it would be, at least to a geek like me.

flight tracking application preview

In the course of finding positive solutions, and easy implementation, but good enough to carry out this idea of mine - I came across Aviation Edge - a global flight database with an API. But this a REST API and does not fulfill my real-time requirement.

And with the advent of event-streaming technologies, many data providers prefer streaming data in real-time using push protocols over letting their consumers poll for data frequently using REST - which is inefficient for both parties.

During this search, I found Ably Hub. It is a marketplace for publicly hosted free, open and commercial data streams by various providers that are available to be consumed. Most of them are free to use. Luckily, I found just the thing that could scratch my itch with this project. A realtime data stream of flight data that uses data from Flight Radar called Heathrow Flights. Even though the flight information coming through in this data stream is limited to flights originating or arriving at London Heathrow airport, this was enough for my project.

If you’ve never heard of Ably, you can read about them from their site to learn more, and probably share what you learn with me too.

For this article, I’ll share how we can build a Realtime flight tracking app in React Native using the data from the Heathrow flight product on the Ably Hub.

Somethings that’ll be nice to have before we get started:

  • Knowledge of JavaScript
  • A free account with Ably
  • Knowledge of React, React Hooks and React Native

Features of the application:

  • Arrival and Departure Screens: each screen will have a list of airports labeled with location and the destination of aircrafts

  • A search box: to quickly find a flight with its IATAID (International Air Transport Association ID)

  • The map area: we’ll have a map that tracks the current location of a moving flight; a Polyline from origin to destination

Step 1 - Creating a free Ably account

If you don't have a free account with Ably, you can create one here. If you already have an account, you can proceed to the Heathrow Flights product on Ably Hub. This is a self-subscribe product.

Subscribing to the product

Locate the 'Subscribe to Heathrow Flight' button by the right corner of the product page as shown above. This will enable your Ably account to access the flight data stream from one or more of your apps. Let's go ahead and set one up next.

Step 2 - Setting up an Ably App

We'll assume we already have an Ably free account and we've subscribed to Ably's Hub Stream Product: Heathrow Flight. Now, we want to create an Ably app.

Create an Ably app

If you've successfully arrived on your dashboard, you should see a blue button labelled 'create app'. Create an Ably app using the button, then enter a project name for the project/app, as shown below.

Add a project name

Step 3 - Using Ably React Native Hub Stream Kit

I’ve created a starter kit to make building our project a lot faster and cleaner. The starter kit contains a quick setup of React Native with Expo Managed Workflow. Expo is highly recommended by the React Native team to build a small (or large project depending on complexity) with React.

Quickly clone the project from GitHub using the command below:

$ git clone https://github.com/ably/tutorials/tree/flight-tracking-app-starter-kit.git

Next, change directory into the project folder cd flight-tracking-app-starter-kit.

You'll want to confirm you have yarn, expo-cli, and nodejs installed on your machine.

Installations:

  • Node.js (https://nodejs.org)
  • Yarn (npm install -g yarn)
  • Expo-CLI (npm install -g expo-client)

We’d use Expo Client to test our application while building. Expo Client is currently available on both iOS and Android stores. Please, create an Expo account here.

Step 4 - Setting up locally

To get started with the project run yarn from the project directory your terminal.

$ cd flight-tracking-app-starter-kit

Next, run $ expo start and also launch the running application on a mobile device.

Step 5 - Obtaining Your Private API_KEY

Locate env.js in the root directory of your project and replace the code with the private API_KEY obtained from the Ably application you created in Step Two. Your API_KEY should look like this:

Ably Private API_KEY

Step 6 - Update and Modify Codes

Your folder structure should like this:
Folder Structure

Navigate to the hooks/ably.hooks.js and update the CHANNEL_SCOPE with this code:

const CHANNEL_NAME = '[product:ably-flightradar24/heathrow-flights]flights:airport:LHR'

Ably Product Identifier

The above channel subscribes to a data stream of airport information like the flights departing and arriving at Heathrow Airport, London.

Step 7 - Connecting to Ably (Pub/Sub)

Ably uses a scalable and resilient messaging mechanism Pub/Sub. Ably Realtime service organizes the message traffic within applications into named channels.

Add the code in ./hooks/ably.hooks.js to connect and subscribe to a named channel.

export const useAblyChannel = (channel, dependencies) => {

  const [onMessage, setOnMessage] = useState('Please wait..')

  const [isLoading, setLoading] = useState(true)
  //fetch channel data
  const [channelData, setChannelData] = useState(null)

  useEffect(() => {
   //add ably connection here 

    console.log('Ran Use Effects', channel)
     // know the current state of the application 
    ably.connection.on(function(stateChange) {
      console.log('New connection state is ' + stateChange.current)
      setOnMessage(stateChange.current)
      setLoading(true)
    })
    // use the channel scope in the application 
    const useChannel = ably.channels.get(`${CHANNEL_NAME}:${channel}`)
    useChannel.subscribe(message => {
      if (message.data.length > 0) {
        setOnMessage('Loading Data...')
        setLoading(false)
        setChannelData(message.data)
      }
    })

  }, dependencies)

  return [isLoading, onMessage, channelData]
}

Also quickly update ./context/ApplicationContext.js to have listeners

export default ApplicationContext = ({ children }) => {
.....

  //add subscription listeners here 
const setChannel = iATA => {
    console.log(`This ${iATA} was clicked`)
    return ably.channels.get(`${HUB_STREAM}:${iATA}`)
  }

  const departureListener = message => {
    console.log('Still Listening Departure', message.data)
    message.data && subscribe(message.data)
  }

  const arrivalListener = message => {
    console.log('Still Listening', message.data)
    message.data && subscribe(message.data)
  }

  const subscribe = data => {
    setIsLoading(false)
    setArrivalsData(data)
  }
 // add unsubscribe listeners here
  const unsubscribe = (useChannel, type) => {
    console.log(`unmounting sub ${type}`)
    useChannel.off()
    useChannel.unsubscribe()
    type === 'arrival' ? setArrivalsData(null) : setDeparturesData(null)
    setIsLoading(true)
  }

  const setArrivals = (iATA, action) => {
    action === 'reset'
      ? unsubscribe(setChannel(iATA), 'arrival')
      : setChannel(iATA).subscribe(arrivalListener)
  }

  const setDepartures = (iATA, action) => {
    action === 'reset'
      ? unsubscribe(setChannel(iATA), 'departure')
      : setChannel(iATA).subscribe(departureListener)
  }

...

}

Next, update the ./screens/ArrivalScreen.js with the code as follows:

import { ListCards } from '../components/ListCards'
import { arrival as channelData } from '../constants/RawData'
import { Container, Text, Content } from 'native-base'

The above imports dummy data structured in the form of a data stream; this gives you a glance of what the expected data looks like.

import React, { useEffect } from 'react'
import { StyleSheet } from 'react-native'
import { Container, Text, Content } from 'native-base'
import { ListCards } from '../components/ListCards'
import { arrival as channelData } from '../constants/RawData'

Next, still in ArrivalScreen.js file, display a ListCard to show the origin and destination of the aircraft we want to watch in realtime. Add the codes below to display a ListCard:

export default ArrivalScreen = ({navigation}) => {
...
const Arrivals = channelData
    ? channelData.map((item, index) => {
        return (
          <ListCards
            key={index}
            text={`${item.origin} - ${item.destination} (${item.iataId})`}
            icon="ios-airplane"
            action={() =>
              navigation.navigate('PopModal', {
                iataId: item.iataId,
                action: 'arrival'
              })
            }
            rotate
          />
        )
      }) : []
...

  return (
    <Container style={styles.container}>
     <Content>{Arrivals}</Content>
    </Container>
  )
...
}

Repeat the steps for ./screens/ArrivalScreen.js in ./screen/DepartureScreen.js, and also rename 'Arrivals' constant to 'Departures'. Rename

...
import { departure as channelData } from '../constants/RawData'

export default DepartureScreen = ({ navigation }) => {

    const Departures = channelData
      ? channelData.map((item, index) => {
          return (
            <ListCards
              key={index}
              text={`${item.origin} - ${item.destination} (${item.iataId})`}
              icon="ios-airplane"
              action={() =>
                navigation.navigate('PopModal', {
                  iataId: item.iataId,
                  action: 'arrival',
                })
              }
            />
          )
        })
      : []

....
  return (
    <Container style={styles.container}>
      <Content>{Departures}</Content>
    </Container>
  )
}

Ensure you save and see changes as shown below in your mobile application before proceeding to Step Seven.

Arrival Screen
Departure Screen

Step 7 - Adding State Management

First, update the React's Context API. Open ./context/ApplicationContext.js
Replace:

const HUB_STREAM = '<HUB_API_STREAM_CHANNEL>'

With:

const HUB_STREAM = '[product:ably-flightradar24/heathrow-flights]flights:plane'

The channel above subscribes to a data stream of aircraft information such as its current location, its route and the speed of the aircraft.

Next, return back to ./screens/ArrivalScreen.js and update code to use React Hooks for the state management of our application: useAblyChannel(channel, [])

export default ArrivalScreen = ({navigation}) => {
...
  const [
    isLoading,
    displayMessage,
    channelData
  ] = useAblyChannel('arrivals', [])
...
...
  return (
    <Container>
      {isLoading ? (
        <LoadingScreen message={displayMessage} />
      ) : (
        <>
          <Content>{Arrivals}</Content>
        </>
      )}
    </Container>
  )
...
}

./screen/DepartureScreen.js

export default DepartureScreen = ({navigation}) => {
...
  const [
    isLoading,
    displayMessage,
    channelData
  ] = useAblyChannel('departures', [])
...
...
  return (
    <Container>
      {isLoading ? (
        <LoadingScreen message={displayMessage} />
      ) : (
        <>
          <Content>{DeparturesScreen}</Content>
        </>
      )}
    </Container>
  )
...
}

Delete this import from both your DepartureScreen and ArrivalScreen since our data will be coming from live data stream using the hooks:

./screens/ArrivalScreens.js

import { arrival as channelData } from '../constants/RawData'

./screens/DepartureScreens.js

import { departures as channelData } from '../constants/RawData'

Next, open up ./components/ListCards.js and add a little bit of spice to make the cards clickable.

export const ListCards = ({ text, rotate, action }) => {
...
  return (
    <Card noShadow>
      <CardItem button onPress={() => handleActions(action)}>
       ...
      </CardItem>
    </Card>
  )
...
}

Adding the button property to the CardItem makes the button clickable.

Save and play around with the project a little bit. Click on a ListCard and to launch the Flight Tracking. Your Application should look like the Gif we have played above.

Step 8: Cleaning up and Finalising

Let's quickly add a search function to our application. Update both ./screen/ArrivalScreen.js and ./screen/DepartureScreen.js

...
import FabButton from '../components/FabButton'

export default DepartureScreen = ({navigation}) => {
...
...
...
return (
    <Container>
      {isLoading ? (
        <LoadingScreen message={displayMessage} />
      ) : (
        <>
          <Content>{Arrivals}</Content>
          <FabButton navigation={navigation} channelData={channelData}/>
        </>
      )}
    </Container>
  )
...
}

Save and right now your application should look and feel like this:
Flight Track Application Preview

You can see the complete demo and source code for this app on GitHub.

Summary

You’ve successfully built a realtime flight tracking application that tracks the real-world flights' location and shows it on a map. How cool!.

Discussion

pic
Editor guide