Weather App
In last article we seen Image gallery app. In this article we see how to build weather app. We have an App component that represents the entire screen and contains the weather information displayed to the use. Inside of this component, we have a SearchInput component that allows us to search for different cities. App is the only component created with a default application using a blank template.
Lets take a look at its file:
_______________________________________________________
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
_______________________________________________________________________________________________
We can also attach methods as properties to classes and the same applies to component classes in react native. We can see we already have one for this component,
the render method:
render() {
return (
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
</View>
);
}
_______________________________________________________________________________________________
What we see on
our device when launching our device matches what we see described in this method. The render() technique is
the main required strategy for a React Native part. React Native uses the return
an incentive from this technique to determine what to deliver for the
component. When we use React Native, we represent
different parts of our application as components. This means we can build our
app using different reusable pieces of logic with each piece displaying a
specific part of our UI. Let’s break down what we already have in terms of
components:
- Our
entire application is rendered with App as our top-level component.
Although created automatically as part of setting up a new Expo CLI
project, this component is a custom component responsible for rendering
what we need in our application
- The
View component is used as a layout container.
- Within
View, we use the Text component to display lines of text in our
application. Unlike App, both View and Text are built-in React Native
components that are imported and used in our custom component.
Our App component uses and returns an HTML-like
structure. This is JSX, which is an extension of JavaScript that allows us
to use an XML-like syntax to
define our UI structure.
JSX
When we build an application with React Native, components ultimately render native views which are displayed on our device. As such, the render() method of a component needs to describe how the view should be represented. In other words, React Native allows us to describe a component’s iOS and Android representation in JavaScript. JSX was created to make the JavaScript representation of components easier to understand. It allows us to structure components and show their hierarchy visually in markup. Consider this JSX snippet:
<View>
<Text style={{ color: 'red'
}}>
Hello, friend! I am a basic React
Native component.
</Text>
</View>
In here, we’ve nested a Text component within a
View component. Notice how we use braces ({}) around an object ({ color: 'red'
}) to set the style property value for Text. In JSX, braces are a delimiter,
signaling to JSX that what resides in-between the braces is a JavaScript
expression. The other delimiter is using quotes for strings, like this:
<TextInput placeholder="This
is a string" />
Props
Props allow us to pass parameters to components to customize their features. Here, View is used to layout the entire content of the screen. We only have a single prop attached, style, that allows us to pass in style parameters to adjust how our View component is rendered on our devices. Each built-in component provided by React Native has its own set of valid props that we can use for customization Like our View component, many components in React Native accept a style prop. However, we can take a look at our styles object at the bottom of App.js and get an idea of how it works:
To build our weather app, we’ll start with layout and styling. Once we have some of the essence of our weather app in place we can begin to explore strategies for managing data. we want our app to display the city, temperature, and weather conditions as separate text fields. Although we’ll eventually interface with a weather API in order to retrieve actual data.
Adding styles:
To get a better handle on styling, let’s try adding an object with a color attribute to one of the text fields:
<View
style={styles.container}>
<Text style={{ color: 'red'
}}>
Open up App.js to start working on
your app!
</Text>
</View>
Although we can style our entire component this way, a lot of
inline styles (or style attributes defined directly within the delimeter of the
style prop) used in a component can make things harder to read and digest. We
can solve this by leveraging React Native’s Stylesheet API to separate our
styles from our component. With Stylesheet, we can create styles with
attributes similar to CSS stylesheets. We can see that Stylesheet is already
imported at the top of the file. It’s used to declare our first style, styles.container,
which we use for View. We can add a new style called red to our styles:
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
red: {
color: 'red',
},
});
We’ll then have Text use this style:
<View
style={styles.container}>
<Text style={styles.red}>
Open up App.js to start working on
your app!
</Text>
</View>
If we save our file and take a look at our app, we can see that the end result is the same. Now let’s add some appropriate styles and text fields in order to display some weather data for a location. To add multiple styles to a single component, we can pass in an array of styles:
<Text style={[styles.largeText, styles.textStyle]}>
San Francisco
</Text>
<Text style={[styles.smallText, styles.textStyle]}>
Light Cloud
</Text>
<Text style={[styles.largeText, styles.textStyle]}>24°</Text>
It is important to mention that when passing an array, the styles at the end of the array take precedence over earlier styles, in case of any repeated attributes. We can see that we’re referencing three new styles; textStyle, smallText, and largeText. Let’s define these within our styles object:
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
textStyle: {
textAlign: 'center',
fontFamily:
Platform.OS === 'ios' ?
'AvenirNext-Regular' : 'Roboto',
},
largeText: {
fontSize: 44,
},
smallText: {
fontSize: 18,
},
- textStyle specifies an alignment (center) as well as the fontFamily. Notice how we use Platform to define platform specific fonts for both iOS and Android. We do this because both operating systems provide a different set of native fonts.
- smallText and largeText both specify different font sizes.
import { StyleSheet, Text, View,
Platform } from 'react-native';
Let’s take a look at our application
now:
The Platform API
allows us to conditionally apply different styles or properties in our
component based on the device’s operating system. The OS attribute of the object
returns either ios or android depending on the user’s device. Although this is a relatively
simple way to apply different properties in our application based on the user’s
device, there may be scenarios where we may want our component to be substantially
different between operating systems. We can also use the Platform. select
method that takes the operating system as keys within an object and returns the
correct result based on the device:
textStyle: {
textAlign: 'center',
...Platform.select({
ios: {
fontFamily:
'AvenirNext-Regular',
},
android: {
fontFamily: 'Roboto',
},
}),
},
Separate files
Instead of applying conditional checks using Platform.OS a number times
throughout the entire component file, we can also leverage the use of platform
specific files instead. We can create two separate files to represent the same
component each with a different extension: .ios.js and .android.js. If both
files export the same component class name, the React Native packager knows to
choose the right file based on the path extension.
Text input
We
now have text fields that display the location, weather condition, and
temperature. The next thing we need to do is provide some sort of input to
allow the user to search for a specific city. Again, we’ll continue using
hardcoded data for now. We’ll only begin using an API for real data once we
have all of our components in place. React Native
provides a built-in TextInput component that we can import into our component
that allows us to accept user input.
<Text style={[styles.largeText,
styles.textStyle]}>
San Francisco
</Text>
<Text style={[styles.smallText,
styles.textStyle]}>
Light Cloud
</Text>
<Text style={[styles.largeText,
styles.textStyle]}>24°</Text>
<TextInput
autoCorrect={false}
placeholder="Search any
city"
placeholderTextColor="white"
style={styles.textInput}
clearButtonMode="always"/>
There are a number of props associated
with TextInput that we can use. Here we’re specifying a placeholder, its color,
as well as a style for the component itself. Let’s create its style object,
textInput, underneath our other styles:
smallText: {
fontSize: 18,
},
textInput: {
backgroundColor: '#666',
color: 'white',
height: 40,
width: 300,
marginTop: 20,
marginHorizontal: 20,
paddingHorizontal: 10,
alignSelf: 'center',
},
Now let’s take a look at our
application:
We
can see that the text input has a default underline on Android. We’ll go over
how to remove this in a bit. We’ve also specified the clearButtonMode prop to
be always. This shows a button on the right side of the input field when
characters are inserted that allows us to clear the text. This is only
available on iOS.
We can now type into the input field! However one thing you may have noticed is that when you focus on the input field with a tap, the keyboard pops up and covers it on Android and comes quite close on iOS.
Since
the virtual keyboard can cover roughly half the device screen, this is a common
problem that occurs when using text inputs in an application. Fortunately, React Native includes KeyboardAvoidingView, a component that solves this
problem by allowing us to adjust where other components render in relation to
the virtual keyboard. Let’s import and use this component instead of View:
render() {
return (
<KeyboardAvoidingView
style={styles.container}
behavior="padding"
>
<Text style={[styles.largeText,
styles.textStyle]}>
San Francisco
</Text>
<Text style={[styles.smallText,
styles.textStyle]}>
Light Cloud
</Text>
<Text style={[styles.largeText,
styles.textStyle]}>24°</Text>
<TextInput
autoCorrect={false}
placeholder="Search any
city"
placeholderTextColor="white"
style={styles.textInput}
clearButtonMode="always"
/>
</KeyboardAvoidingView>
);
}
KeyboardAvoidingView
accepts a behavior prop with which we can customize how the keyboard adjusts.
It can change its height, position or bottom padding in relation to the
position of the virtual keyboard. Here, we’ve specified padding. And finally,
it’s important to double check our imports to make sure we have everything that
we’re using
import React from 'react';
import {
StyleSheet,
Text,
KeyboardAvoidingView,
Platform,
TextInput,
} from 'react-native';
Now
tapping the text input will shift our component text and input fields out of
the way of the software keyboard.
we’ve
explored how to add styling into our application, and we’ve included some
built-in components into our main App component. We use View as our component
container and import Text and TextInput components in order to display
hardcoded weather data as well as an input field for the user to change
locations. It’s important to re-iterate that React Native is component-driven.
We’re already representing our application in terms of components that describe
different parts of our UI without too much effort, and this is because React
Native provides a number of different built-in components that you can use
immediately to shape and structure your application. However, as our
application begins to grow, it’s important to begin thinking of how it can
further be broken down into smaller and simpler chunks. We can do this by
creating custom components that contain a small subset of our UI that we feel
fits better into a separate, distinct component file. This is useful in order
to allow us to further split parts of our application into something more
manageable, reusable and testable. Although our application in its current
state isn’t extremely large or unmanageable, there’s still some room for
improvement. The first way we can refactor our component is to move our
TextInput into a separate component to hide its implementation details from the
main App component. Let’s create a components directory in the root of the application
with the following file:
── components/
- SearchInput.js
All
the custom components we create that we use in our main App component will live
inside this directory. For more advanced apps, we might create directories
within components to categorize them more specifically. Since this app is
pretty simple, let’s use a flat components directory. The SearchInput will be
our first custom component so let’s move all of our code for TextInput from
App.js to SearchInput.js
import React from 'react';
import { StyleSheet,
TextInput, View } from 'react-native';
export default class
SearchInput extends React.Component {
render() {
return (
<View
style={styles.container}>
<TextInput
autoCorrect={false}
placeholder={this.props.placeholder}
placeholderTextColor="white"
underlineColorAndroid="transparent"
style={styles.textInput}
clearButtonMode="always"
/>
</View>
);
}
}
const styles =
StyleSheet.create({
container: {
height: 40,
width: 300,
marginTop: 20,
backgroundColor: '#666',
marginHorizontal: 40,
paddingHorizontal: 10,
borderRadius: 5,
},
textInput: {
flex: 1,
color: 'white',
},
});
Let’s break down what this file contains:
• We export a component named SearchInput.
• This component accepts a placeholder prop.
• This component returns a React Native TextInput with a few of its properties specified wrapped within a View.
• We’ve applied the appropriate styles to our view container including a borderRadius.
• We also added underlineColorAndroid="transparent" to
remove the dark underline that shows by default on Android.
Custom props
As you may recall, in App.js we set the placeholder prop for TextInput to “Search any city.” That renders the text input with a placeholder: For SearchInput, we could hardcode a string again for placeholder. We can also create props for custom components that we build as well. That’s what we do here in SearchInput. The component accepts the prop placeholder. In turn, SearchInput uses this value to set the placeholder prop on TextInput. The way data flows from parent to child in React Native is through props. When a parent renders a child, it can send along props the child depends on. A component can access all its props through the object this.props. If we decide to pass down the string "Type Here" as the placeholder prop, the this.props object will look like this: { "placeholder": "Type Here" } In here, we’ll set up App to render SearchInput which means that App is the parent of SearchInput. Our parent component will be responsible for passing down the actual value of placeholder.
Importing components
In
order to use SearchInput in App, we need to import the component first. We can
remove the TextInput logic from App.js and have App use SearchInput instead:
import React from 'react';
import {
StyleSheet,
Text,
KeyboardAvoidingView,
Platform,
} from 'react-native';
import SearchInput from
'./components/SearchInput';
export default class App extends
React.Component {
render() {
return (
<KeyboardAvoidingView
style={styles.container}
behavior="padding"
>
<Text style={[styles.largeText,
styles.textStyle]}>
San Francisco
</Text>
<Text style={[styles.smallText,
styles.textStyle]}>
Light Cloud
</Text>
<Text style={[styles.largeText,
styles.textStyle]}>24°</Text>
<SearchInput
placeholder="Search any city" />
</KeyboardAvoidingView>
);
}
}
By moving the entire TextInput details into a separate component called SearchInput, we’ve made sure to not have any of its specific implementation details showing in the parent component anymore. We can also remove the text input’s styling defined within the styles object. React Native was built in order to allow us to layout our entire application in terms of self-contained components, and that means we should separate parts of our application into distinct units with custom functionality attached to them. This allows us to build a more manageable application that’s easier to control and understand.
Background image
we can make our application more visually
appealing by displaying a background image that represents the current weather
condition. we’ve included a number of images for various weather conditions. If
you inspect the weather/assets directory, you’ll find images like clear.png,
hail.png, and showers.png. If you’re following along, copy these two folders
over from the sample code into your project:
1. weather/assets
2. weather/utils
With the assets and utils folders
copied over, let’s update our App component:
import React from 'react';
import {
StyleSheet,
View,
ImageBackground,
Text,
KeyboardAvoidingView,
Platform,
} from 'react-native';
import getImageForWeather from
'./utils/getImageForWeather';
import SearchInput from
'./components/SearchInput';
export default class App extends
React.Component {
render() {
return (
<KeyboardAvoidingView
style={styles.container}
behavior="padding"
>
<ImageBackground
source={getImageForWeather('Clear')}
style={styles.imageContainer}
imageStyle={styles.image}
>
<View
style={styles.detailsContainer}>
<Text style={[styles.largeText,
styles.textStyle]}>
San Francisco
</Text>
<Text style={[styles.smallText,
styles.textStyle]}>
Light Cloud
</Text>
<Text style={[styles.largeText,
styles.textStyle]}>
24°
</Text>
<SearchInput
placeholder="Search any city" />
</View>
</ImageBackground>
</KeyboardAvoidingView>
);
}
}
In
this component, we’re importing a getImageForWeather method from our utils
directory which returns a specific image from the assets directory depending on
a weather type. For example, getImageForWeather('Clear') returns the following
image: