React Native - Building a Minimalist Weather App using Expo XDE

2018-03-27

React Native is a great framework to develop cross-platform mobile applications for the platforms iOS and Android. In this, I am going to take you through the process of building a “minimalist” weather application using React Native by fetching real-time data. If you have never worked with React Native, you can use this walkthrough as kickstart in your journey of becoming a mobile application developer and will be a cool project for your portfolio.

Getting Started: Requirements

You have some experience of working your way with Reactjs, you will have no problem following this tutorial. If you newbie to JavaScript or Reactjs ecosystem, I want to halt right here and go through this awesome resource that can help you with understanding the basic concepts in this tutorial. Don’t spend too much time if you are not interested in building web applications using Reactjs, just go through the nitty-gritty.

Please note that React Native is not a hybrid mobile app framework like others available. It uses a bridge between Javascript and native APIs of a specific platform. Do take a look at React Native Official Docs to read more about this.

I will be using Expo which is described as “the fastest way to build an app”. It is an open-source set of tools and services that come in handy, especially if you are getting started in the React Native world. The development tool I am going to use for Expo is Expo XDE.

Requirements summary

  • You know how to write JavaScript
  • Familiar with React
  • Nodejs installed on your local machine
  • Simple npm commands

That’s all. Let us get started with the development process.

Getting Started: In real this time

Open the Expo XDE after its installation and click on the “Create New Project”.

Enter the name of your application and click on “Create”. The name of the application will be in lowercase, I don’t know why, Expo XDE UI does not support uppercase characters.

Expo, behind the scenes using React Native Package manager to simulate the application and the load dependencies from the app’s package.json file. The benefit of using Expo XDE is that you do not have to open multiple terminal windows and you can test the app while still developing on a real device. Once it is done with the process of generating a source code of our app, we can start it in a simulator on our local machine to see the default app it comes with.

If you are on Mac, make sure, you have Xcode installed. If you are using Windows, please follow the instructions to install Android Studio to run the simulator.

If you want to skip simulating the app and run it on an actual device without generating any .apk or .ipa, install the Expo client and scan the QR code generated by default by Expo XDE.

Once, bundling of the source code is done you will be prompt with a success message on the Expo XDE terminal:

And you will be able to see that our default app is running on the device:

The message displayed here is the same code that is rendered by App.js in the root of our app.

1import React from 'react'
2import { StyleSheet, Text, View } from 'react-native'
3
4export default class App extends React.Component {
5 render() {
6 return (
7 <View style={styles.container}>
8 <Text>Minimalist Weather App</Text>
9 </View>
10 )
11 }
12}
13
14const styles = StyleSheet.create({
15 container: {
16 flex: 1,
17 backgroundColor: '#fff',
18 alignItems: 'center',
19 justifyContent: 'center'
20 }
21})

Change the <Text> to:

1<Text>Minimalist Weather App</Text>

and you will see the output being rendered and the app is reloaded live. You don’t have to refresh it to see the changes.

This completes our first getting started step. In the next step, we will build a static prototype of what our app is going to look like.

The Prototype

In this step, we will be developing our first screen, that is going to be loading screen.

In your App.js, define a local state:

1import React from 'react'
2import { StyleSheet, Text, View } from 'react-native'
3
4export default class App extends React.Component {
5 state = {
6 isLoading: false
7 }
8
9 render() {
10 const { isLoading } = this.state
11 return (
12 <View style={styles.container}>
13 {isLoading ? null : (
14 <View>
15 <Text>Minimalist Weather App</Text>
16 </View>
17 )}
18 </View>
19 )
20 }
21}
22
23const styles = StyleSheet.create({
24 container: {
25 flex: 1,
26 backgroundColor: '#fff',
27 alignItems: 'center',
28 justifyContent: 'center'
29 }
30})

The above code states that when our local state object isLoading is false, we will show the name of the application. This is what we are going to render. Later on, instead of displaying the name of application we will be showing the weather here once our API has successfully fetches the data. For now, I am sticking to this message because first, we are going to work on, what if our app is in the state of loading? Let's add a text message to indicate that the app is fetching the data.

1import React from 'react'
2import { StyleSheet, Text, View, Animated } from 'react-native'
3
4export default class App extends React.Component {
5 state = {
6 isLoading: true
7 }
8
9 render() {
10 const { isLoading } = this.state
11 return (
12 <View style={styles.container}>
13 {isLoading ? (
14 <Text>Fetching The Weather</Text>
15 ) : (
16 <View>
17 <Text>Minimalist Weather App</Text>
18 </View>
19 )}
20 </View>
21 )
22 }
23}
24
25const styles = StyleSheet.create({
26 container: {
27 flex: 1,
28 backgroundColor: '#fff',
29 alignItems: 'center',
30 justifyContent: 'center'
31 }
32})

When our app is done loading the data from the API, we will set the state of isLoading to false.

First Screen

We will define a new Weather component at ./components/Weather.js. The boilerplate code for every weather condition screen is going to be the same. It will be divided into two views, a header, and a body. The header will show the weather condition icon and temperature and the body will display the text associated with the weather condition.

In Weather.js, we will start by defining two containers inside the main container: headerContainer and bodyContainer. Do note that we are defining Weather component not as a class but a function in order to receive props and since it will not be managing a state.

1import React from 'react'
2import { View, Text, Stylesheet } from 'react-native'
3
4const Weather = () => {
5 return (
6 <View style={styles.container}>
7 <View style={styles.headerContainer} />
8 <View style={styles.bodyContainer} />
9 </View>
10 )
11}
12
13const styles = StyleSheet({
14 container: {
15 flex: 1
16 },
17 headerContainer: {},
18 bodyContainer: {}
19})
20
21export default Weather

We will be using MatericalCommunityIcons that comes with expo (one of the perks) as a sub-library of a humongous library called vector-icons.

1import React from 'react'
2import { View, Text, StyleSheet } from 'react-native'
3import { MaterialCommunityIcons } from '@expo/vector-icons'
4
5const Weather = () => {
6 return (
7 <View style={styles.weatherContainer}>
8 <View style={styles.headerContainer}>
9 <MaterialCommunityIcons size={48} name="weather-sunny" color={'#fff'} />
10 <Text style={styles.tempText}>Temperature˚</Text>
11 </View>
12 <View style={styles.bodyContainer}>
13 <Text style={styles.title}>So Sunny</Text>
14 <Text style={styles.subtitle}>It hurts my eyes!</Text>
15 </View>
16 </View>
17 )
18}
19
20const styles = StyleSheet.create({
21 weatherContainer: {
22 flex: 1,
23 backgroundColor: '#f7b733'
24 },
25 headerContainer: {
26 flex: 1,
27 alignItems: 'center',
28 justifyContent: 'center'
29 },
30 tempText: {
31 fontSize: 48,
32 color: '#fff'
33 },
34 bodyContainer: {
35 flex: 2,
36 alignItems: 'flex-start',
37 justifyContent: 'flex-end',
38 paddingLeft: 25,
39 marginBottom: 40
40 },
41 title: {
42 fontSize: 48,
43 color: '#fff'
44 },
45 subtitle: {
46 fontSize: 24,
47 color: '#fff'
48 }
49})
50
51export default Weather

This how our app looks after the prototypal stage is complete.

Fetching The Data

To fetch real-time weather data I found Open Weather Map API to be highly useful and consistent. To communicate with the API you are going to need an API key. Register yourself as a user on the site, and get your API key. Please note that it takes at least 10 minutes for Open Weather API to activate the API key. Once it is available, tag along.

Go to the API section and you will see that our need is satisfied by the Current Weather data. I am going to store my API key in ./utils/WeatherAPIKey.js file. I know not the best name for a file.

1export const API_KEY = 'YOUR_API_KEY HERE'

The way the Open Weather API works is that we need to provide it coordinates using device’s location in terms of longitude and latitude. It will then fetch the data from its server which will be a JSON object. From the server, right now we need two things, the temperature, and the weather condition. We should have temperature and the weather condition stored in our local state in App.js.

1import React from 'react'
2import { StyleSheet, Text, View, Animated } from 'react-native'
3
4import { API_KEY } from './utils/WeatherAPIKey'
5
6import Weather from './components/Weather'
7
8export default class App extends React.Component {
9 state = {
10 isLoading: false,
11 temperature: 0,
12 weatherCondition: null,
13 error: null
14 }
15
16 componentDidMount() {
17 navigator.geolocation.getCurrentPosition(
18 position => {
19 this.fetchWeather(position.coords.latitude, position.coords.longitude)
20 },
21 error => {
22 this.setState({
23 error: 'Error Gettig Weather Condtions'
24 })
25 }
26 )
27 }
28
29 fetchWeather(lat = 25, lon = 25) {
30 fetch(
31 `http://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&APPID=${API_KEY}&units=metric`
32 )
33 .then(res => res.json())
34 .then(json => {
35 console.log(json)
36 })
37 }
38
39 render() {
40 const { isLoading } = this.state
41 return (
42 <View style={styles.container}>
43 {isLoading ? <Text>Fetching The Weather</Text> : <Weather />}
44 </View>
45 )
46 }
47}
48
49const styles = StyleSheet.create({
50 container: {
51 flex: 1,
52 backgroundColor: '#fff'
53 }
54})

We start by importing the API key we just defined and then updating our state with temperature, weatherCondition, and error. We are using componentDidMount() a lifecycle method which helps us re-render once our API is done fetching the data. It will also help us in updating the state. We are also using JavaScript navigator API to get the current location. This is where a JavaScript API will communicate with a native one using a bridge. We pass on the values of latitude and longitude to our custom function fetchWeather where the API of Open Weather Map is called.

The result we get is in JSON format, and if you console log it, you will be able to see the result as a JSON object in Expo terminal where there are a lot of values. We only need the value of temperature and the weather condition. We then update local state with the new values obtained. &units=metric at the end of our API call converts the temperature from Kelvin to Celsius.

1.then(json => {
2 // console.log(json);
3 this.setState({
4 temperature: json.main.temp,
5 weatherCondition: json.weather[0].main,
6 isLoading: false
7 });

Now, all we have to do is pass the value two of our local state as props to the Weather Component and then update it such that it can receive those props.

First, in App.js:

1<Weather weather={weatherCondition} temperature={temperature} />

Update the Weather.js:

1const Weather = ({ weather, temperature }) => {
2 return (
3 <View style={styles.weatherContainer}>
4 <View style={styles.headerContainer}>
5 <MaterialCommunityIcons size={48} name="weather-sunny" color={'#fff'} />
6 <Text style={styles.tempText}>{temperature}˚</Text>
7 </View>
8 <View style={styles.bodyContainer}>
9 <Text style={styles.title}>{weather}</Text>
10 <Text style={styles.subtitle}>It hurts my eyes!</Text>
11 </View>
12 </View>
13 )
14}

Since we have done the hard part of fetching the real-time data, we must make Weather component behave dynamically to the values it is getting. All this dynamic part is going to be associated with one thing which we are getting from our local state, weatherCondition.

Dynamic Behaviour

Using weatherCondition we can define the background changes, title, subtitle and weather icon changes. Let's start by pre-defining weather conditions in a file ./utils/WeatherConditions.js.

1export const weatherConditions = {
2 Rain: {
3 color: '#005BEA',
4 title: 'Raining',
5 subtitle: 'Get a cup of coffee',
6 icon: 'weather-rainy'
7 },
8 Clear: {
9 color: '#f7b733',
10 title: 'So Sunny',
11 subtitle: 'It is hurting my eyes',
12 icon: 'weather-sunny'
13 },
14 Thunderstorm: {
15 color: '#616161',
16 title: 'A Storm is coming',
17 subtitle: 'Because Gods are angry',
18 icon: 'weather-lightning'
19 },
20 Clouds: {
21 color: '#1F1C2C',
22 title: 'Clouds',
23 subtitle: 'Everywhere',
24 icon: 'weather-cloudy'
25 },
26
27 Snow: {
28 color: '#00d2ff',
29 title: 'Snow',
30 subtitle: 'Get out and build a snowman for me',
31 icon: 'weather-snowy'
32 },
33 Drizzle: {
34 color: '#076585',
35 title: 'Drizzle',
36 subtitle: 'Partially raining...',
37 icon: 'weather-hail'
38 },
39 Haze: {
40 color: '#66A6FF',
41 title: 'Haze',
42 subtitle: 'Another name for Partial Raining',
43 icon: 'weather-hail'
44 },
45 Mist: {
46 color: '#3CD3AD',
47 title: 'Mist',
48 subtitle: "Don't roam in forests!",
49 icon: 'weather-fog'
50 }
51}

These weather conditions are provided from Open Weather API here. Then, let’s import this file in our Weather.js. We will also define PropTypes now for the two props we are receiving from App.js. Take a look below, it is simple.

1import React from 'react'
2import { View, Text, StyleSheet } from 'react-native'
3import { MaterialCommunityIcons } from '@expo/vector-icons'
4import PropTypes from 'prop-types'
5import { weatherConditions } from '../utils/WeatherConditions'
6
7const Weather = ({ weather, temperature }) => {
8 return (
9 <View
10 style={[
11 styles.weatherContainer,
12 { backgroundColor: weatherConditions[weather].color }
13 ]}
14 >
15 <View style={styles.headerContainer}>
16 <MaterialCommunityIcons
17 size={72}
18 name={weatherConditions[weather].icon}
19 color={'#fff'}
20 />
21 <Text style={styles.tempText}>{temperature}˚</Text>
22 </View>
23 <View style={styles.bodyContainer}>
24 <Text style={styles.title}>{weatherConditions[weather].title}</Text>
25 <Text style={styles.subtitle}>
26 {weatherConditions[weather].subtitle}
27 </Text>
28 </View>
29 </View>
30 )
31}
32
33Weather.propTypes = {
34 temperature: PropTypes.number.isRequired,
35 weather: PropTypes.string
36}
37
38const styles = StyleSheet.create({
39 weatherContainer: {
40 flex: 1
41 },
42 headerContainer: {
43 flex: 1,
44 flexDirection: 'row',
45 alignItems: 'center',
46 justifyContent: 'space-around'
47 },
48 tempText: {
49 fontSize: 72,
50 color: '#fff'
51 },
52 bodyContainer: {
53 flex: 2,
54 alignItems: 'flex-start',
55 justifyContent: 'flex-end',
56 paddingLeft: 25,
57 marginBottom: 40
58 },
59 title: {
60 fontSize: 60,
61 color: '#fff'
62 },
63 subtitle: {
64 fontSize: 24,
65 color: '#fff'
66 }
67})
68
69export default Weather

Most of the source code is same. We are now just making some additions by using available props with weather conditions and to dynamically change the background, icon, weather name, and the subtitle. You can play around with the styling to make it look more minimalistic or more exquisite, it is up to you.

Note: Before running the application on your actual device make sure you have internet access and location “on” the device for this app to work. We haven’t talked about App Permissions in this article and it is a bit out of the scope too.

The whole code for this application is available at this Github Repo. I have also published the application on Expo Store here for you to test out. Just scan the QR code and run the application to see what you will be working in this tutorial.

Originally this article was published on Blog.expo.io

I'm Aman working as an independent fullstack developer with technologies such as Node.js, ReactJS, and React Native. I try to document and write tutorials to help JavaScript, Web and Mobile developers.