Build Maps and Save Locations , by using React Native-Redux-Navigation-Icons-MAPs — Part-IV

Check Part-III

Hasan Dader
14 min readApr 24, 2019

Welcome to the last and most interesting part of this article.

In the previous part I explained how I used react-native-navigation lib and redux in the app. I created place list screen and connected it to redux, that I connected it to the state and to delete action. After that I registered that screen at react-native-navigation I told the navigation to start the app from that screen. While I was registering the screen at navigation I did wrap the app by redux by using “Provider” property from “react-redux” lib.

After doing all of that I see that now is the perfect time to start to Maps. So how to insert the map into our apps? How to select specific locations from it? And how to save them? How to detect our current location? How to create a marker for the map? And what are the problems we may face when creating the map and how to solve them? We’ll see all of that in this article, it will be exciting and useful one.

So let’s start… 🌎🏍

1. Installation :

First thing we need to do is to install react-native-maps, so type this in the terminal,

npm install react-native-maps --save

after installing is done you need to link the library with your app so go to this link and you’ll find some little and simple steps to do, do them and you can say that you have installed the library successfully.

While doing those steps, in the part of linking the library to android, at the second step there is something that I don’t prefer to do, which is this part,

buildscript {...}
allprojects {...}

/**
+ Project-wide Gradle configuration properties
*/
ext {
compileSdkVersion = 26
targetSdkVersion = 26
buildToolsVersion = "26.0.2"
supportLibVersion = "26.1.0"
googlePlayServicesVersion = "11.8.0"
androidMapsUtilsVersion = "0.5+"
}

I don’t prefer to add this part because the “sdk version” may conflict with the one you use which will cause problems in your app.

In the third step you need to insert your own Google Maps API Key, and you’ll find a link over there for a web site from where you can get your own API Key. And here I’ll show you how to do so,

  1. Click “GET STARTED” button from the first screen,

2. From the next screen select “Maps” and then click “CONTINUE”, as following,

3. From the coming screen select “Select or create project” and type a project name, then click “NEXT”, as following,

4. You’ll be redirected to a new page with a pop-up screen like the image below, from there click “NEXT”, that will get you to a new pop-up screen where you’ll see your API Key, copy it from there and paste it in the appropriate place as shown in step 3 in installation page of react-native-maps.

2. Creating Main Screen & Map:

Well, now we have maps library installed in our project and we have our own API Key, so we can create the main screen of the app and display the map at that screen. How to do so?! let’s look at the code 🤓,

import React, { Component } from 'react';
import { View, Text, TextInput, Button, StyleSheet } from 'react-native';
import { connect } from 'react-redux';
import { addPlaces } from '../../store/actions/index';
import PickLocation from '../../components/PickLocation/PickLocation';
class Map extends Component {state = {
placeName: "",
controls: {
location: {
value: null,
valid: false
}
}
};
placeNameChangedHandler = val => {
this.setState(prevState => {
return {
...prevState,
placeName: val
};
});
};
locationPickedHandler = location => {
this.setState(prevState => {
return {
controls: {
...prevState.controls,
location: {
value: location,
valid: true
}
}
};
});
};
locationAddedHandler = () => {
this.props.onAddLocation(
this.state.placeName,
this.state.controls.location.value
);
this.props.navigator.push({
screen: "pick-places.PlacesScreen",
title: "Places"
});
};
render() {return (
<View style={styles.container}>
<PickLocation onLocationPick={this.locationPickedHandler} />
<View style={styles.button}>
<TextInput
placeholder="Type a Place Name"
value={this.state.placeName}
onChangeText={this.placeNameChangedHandler}
style={styles.placeInput}
/>
<Button title="Save Location" onPress={this.locationAddedHandler} />
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center"
},
placeholder: {
borderWidth: 1,
borderColor: "black",
backgroundColor: "#eee",
width: "80%",
height: 150
},
button: {
margin: 8
},
previewImage: {
width: "100%",
height: "100%"
},
placeInput: {
width: "70%"
}
});
const mapDispatchToProps = dispatch => {
return {
onAddLocation: (placeName, location) =>
dispatch(addPlaces(placeName, location))
};
};
export default connect(null, mapDispatchToProps)(Map);

Let’s look at the head of the code,

import React, { Component } from 'react';
import { View, Text, TextInput, Button, StyleSheet } from 'react-native';
import { connect } from 'react-redux';
import { addPlaces } from '../../store/actions/index';
import PickLocation from '../../components/PickLocation/PickLocation';

we import some regular things to create the class, and we import connect property from react-redux to connect the screen with redux. And because the user will select places from the map and add them to the place list from inside this screen we need to import “addPlaces” action which we have created at redux, so that we can access the state and add the new place information there.

In the last import we import a component that we are supposed to be created, which called “PickLocation”. I will show you how to create that component after finishing this class. In that component we are going to import react-native-maps lib and add MapView tag to display the map. Let’s leave these details to the time we create that component and now let’s go ahead in our class.

state = {
placeName: "",
controls: {
location: {
value: null,
valid: false
}
}
};

Here I added a state to the class to keep some information about the selected place, I keep place name and location information. But wait…! why do we use state here after we have struggled with redux and put the state over there? Well, keeping the state in redux doesn’t mean that we are prevented from using state inside classes. I used the state in this class because I need to check these variables before sending them to redux, such that I want to check that the location data is not empty.

render() {return (
<View style={styles.container}>
<PickLocation onLocationPick={this.locationPickedHandler} />
//SOME CODE
<Button title="Save Location" onPress={this.locationAddedHandler} />
</View>
</View>
);
}
}

Here we call “PickLocation” component to display the map, and get the information of selected place to send them to redux store, we do that at “locationPickedHandler” function which is in this class. Then we create a button to send all the data about the selected place by using “locationAddedHandler” function.

*locationPickedHandler function,

locationPickedHandler = location => {
this.setState(prevState => {
return {
controls: {
...prevState.controls,
location: {
value: location,
valid: true
}
}
};
});
};

Well, when we get location information from “PickLocation” component we send that information to this function, where we call the previous state and save every thing in it but only we change the value of location and its validation.

*locationAddedHandler function,

locationAddedHandler = () => {
this.props.onAddLocation(
this.state.placeName,
this.state.controls.location.value
);
this.props.navigator.push({
screen: "pick-places.PlacesScreen",
title: "Places"
});
};

when we get all information and the user want to add them to his/her place list we call this function, which trigger “addPlaces” action to send the data to redux store, and in the same time it use navigator to push the second page “place list” screen where the saved places are displayed,

.navigator.push function gets two parameters, first one is the screen unique name and the second one is a name we give for that screen, that name will be shown to the user.

I want to go a little bit back to the call of “addPlaces” action, I said that we trigger addPlaces action while I wrote onAddLocation. The reason behind that is as I explained in the previous part we call the action and assign it to another function name to use it in our class. And we do that like the following,

const mapDispatchToProps = dispatch => {
return {
onAddLocation: (placeName, location) =>
dispatch(addPlaces(placeName, location))
};
};

don’t forget to assign the required parameters to the action.

Then we connect the class to redux,

export default connect(null, mapDispatchToProps)(Map);

the connect function generally gets two parameters, the first one is for the state and the second one is for the actions. Here because we only need to call the action we assign the second parameter and left the first one null. In the previous part I needed both state and actions, you can see that code from here.

By this we finished the creation of the main screen, but we still need to create “PickLocation” component.

* PickLocation component :

This component does:

  1. Call react-native-maps lib,
  2. Display the map with a predefined initial region,
  3. Select a specific location,
  4. Create a marker,
  5. Detecting the current location of the user,
  6. Animate the movement of the map.

I will insert the code then explain it part-by-part,

import React, { Component } from 'react';
import { View, Image, Button, StyleSheet, Text, Dimensions } from 'react-native';
import MapView from 'react-native-maps';
class PickLocation extends Component {
state = {
focusedLocation: {
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.015,
longitudeDelta: 0.0121
},
locationChosen: false
};
pickLocationHandler = event => {
const coords = event.nativeEvent.coordinate;
this.map.animateToRegion({
...this.state.focusedLocation,
latitude: coords.latitude,
longitude: coords.longitude
});
this.setState(prevState => {
return {
focusedLocation: {
...prevState.focusedLocation,
latitude: coords.latitude,
longitude: coords.longitude
},
locationChosen: true
};
});
this.props.onLocationPick({
latitude: coords.latitude,
longitude: coords.longitude
});
};
getLocationHandler = () => {
navigator.geolocation.getCurrentPosition(pos => {
const coordsEvent = {
nativeEvent: {
coordinate: {
latitude: pos.coords.latitude,
longitude: pos.coords.longitude
}
}
};
this.pickLocationHandler(coordsEvent);
},
err => {
console.log(err);
alert("Fetching the Position failed, please pick one manually!");
})
}
render() {
let marker = null;
if (this.state.locationChosen) {
marker = <MapView.Marker coordinate={this.state.focusedLocation} />;
}
return (
<View style={styles.container}>
<MapView
initialRegion={this.state.focusedLocation}
style={styles.map}
onPress={this.pickLocationHandler}
ref={ref => this.map = ref}
>
{marker}
</MapView>
<View style={styles.button}>
<Button title="Where am I" onPress={this.getLocationHandler} />
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
width: "100%",
alignItems: "center"
},
map: {
width: "100%",
height: 250
},
button: {
margin: 8
}
});
export default PickLocation;

In the head we called “MapView” from react-native-maps lib,and that what we need actually, we need to view the map.

import MapView from 'react-native-maps';

the state,

The map view needs a location to start from. And need to know how much to zoom in/out that place. I assign that information to the variables at the state, let’s see that,

state = {
focusedLocation: {
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.015,
longitudeDelta: 0.0121
},
locationChosen: false
};

these are the information.

Latitude and longitude tells the map the place we want to look at, so the map shows us that specific place. Then latitudeDelta and longitudeDelta are data that tells the map how much it should be zoomed in/out. Let’s look at this image,

this is a specific part of the map. By taking the red square as a reference, making the value of latitudeDelta or longitudeDelta higher or lower means that the square height and width will increase or decrease. Let’s say that the height increased that means more latitude is viewed and vice versa. In other words, giving higher value to latitudeDelta and longitudeDelta means zoom out which means larger region is shown. But giving them smaller value means zoom in, that’s it.

Well, after giving the information of start location and zoom it’s time to show the map,

render() {
let marker = null;
if (this.state.locationChosen) {
marker = <MapView.Marker coordinate={this.state.focusedLocation} />;
}
return (
<View style={styles.container}>
<MapView
initialRegion={this.state.focusedLocation}
style={styles.map}
onPress={this.pickLocationHandler}
ref={ref => this.map = ref}
>
{marker}
</MapView>
<View style={styles.button}>
<Button title="Where am I" onPress={this.getLocationHandler} />
</View>
</View>
);
}

to show the map as you see we call MapView tag, and inside it we assign initialRegion information here we get that information from the state where we gave those information, then we give the map height and width using the style. I’ll come back to the other properties.

Tell here we viewed the map and gave it an initial region. Now what if the user want to select a specific location by clicking on the map! For that we use “onPress” property inside MapView tag, and from there I call a function called “pickLocationHandler” which I created at that component to get the information of that location, let’s look at it,

pickLocationHandler = event => {
const coords = event.nativeEvent.coordinate;
this.map.animateToRegion({
...this.state.focusedLocation,
latitude: coords.latitude,
longitude: coords.longitude
});
this.setState(prevState => {
return {
focusedLocation: {
...prevState.focusedLocation,
latitude: coords.latitude,
longitude: coords.longitude
},
locationChosen: true
};
});

this.props.onLocationPick({
latitude: coords.latitude,
longitude: coords.longitude
});
};

in the bold part of the code first we use an event that gives us the coordinates of the place that the user clicked on, check this link for more information about events used at maps. In the coords object which is returned to us their exist latitude and longitude data. We need to put these information in the state , so that in the next bold part of the code we assign the data to the appropriate variables in the state. Then in the next code line we call these data again to send them to the main screen from where they will be sent to the “place list” at redux store. In the beginnings of this function you see this call “this.map.animateToRegion” I used it to make the map move from one point to another smoothly, try not to use it in your app and you will see that the map moves sharply. To that function we assign the new latitude and longitude information. There is a question here, that how could we use a map method outside the MapView tag?!! Well, we could do that using a “reference” to MapView tag, we did that by writing this line inside the tag,

ref={ref => this.map = ref}

“ref” is a default react feature, in there we have to pass an inline function and from there we can reach our class using “this” keyword and we gave the reference the name “map”. Then when we call this reference from outside the tag we can think that we are inside the tag. So when we use it like the following we are considered to be writing inside the tag,

this.map.animateToRegion({
...this.state.focusedLocation,
latitude: coords.latitude,
longitude: coords.longitude
});

By this step the user can see the map and select locations from it but when the user click on some place he/she will not see a marker at that place, so let’s add a marker.

Inside the render I wrote this code,

let marker = null;if (this.state.locationChosen) {
marker = <MapView.Marker coordinate={this.state.focusedLocation} />;
}

When the user clicks on a specific place at the map we detect that using locationChosen variable, which exists in the state. It become true when a place is selected. So here I check whether there is a selected place or not, and if exists I call “Marker” property from the MapView and give it the location where I want the marker to be shown. After that I call the marker when I use MapView tag.

Until here the user can use the map without problems, but let us make the user find his/here own location by pressing on a button. Look at this,

<View style={styles.button}>
<Button title="Where am I" onPress={this.getLocationHandler} />
</View>

and this,

getLocationHandler = () => {
navigator.geolocation.getCurrentPosition(pos => {
const coordsEvent = {
nativeEvent: {
coordinate: {
latitude: pos.coords.latitude,
longitude: pos.coords.longitude
}
}
};
this.pickLocationHandler(coordsEvent);
},
err => {
console.log(err);
alert("Fetching the Position failed, please pick one manually!");
})
}

In the first part we create a button and call a function when it is pressed.

In that function we call “getCurrentPosition” method from geolocation

object which we call from navigator object. But what navigator does here!! Aren’t we working with map? Well, navigator is a browser feature used in React, but here we will just use the native device capabilities of getting a user position.

So, getCurrentPosition method takes two parameters and both of them are functions, the first function is to detect the location, and the second one is to detect errors if occurs.

In the first parameter we create our coordsEvent, which will be a javascript object, and inside it we create a property which also going to be a javascript object which is nativeEvent, and inside it another object called coordinate which includes latitude and longitude, in this way we get this current position of the user. What actually we did in this function is the same as what we did in pickLocationHandler function. And the reason behind that chain we made in the function is that to be able to access an event in map we need to use nativeEvent and then we call the event we need which is coordinate and the response of that event is latitude and longitude, and that’s actually what we need, latitude and longitude.

After getting the position we send it to pickLocationHandler function to do its work.

If this first parameter of getCurrentPosition function didn’t success the other function will be called which give the error message.

We finished writing this function and now we can get the user location BUT wait a little bit an important thing is left which is to add a permission to access the user’s location, and to do so go to android → AndroidManifest.xml and add this line,

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

Well, like this we could detect the user location. So congratulations! 🎇🎉

Note: In the emulator the real user location will not be shown, instead a preset location will be shown. But when installing the app in a real device the real location will be detected.

Let us pass to the last piece of this article, problems and solving them.

3. Problems and Solutions :

One problem you may face is after installing react-native-maps lib and trying to run the project you may end up with this error,

Error: Program type already present: android.support.v4.accessibilityservice.AccessibilityServiceInfoCompat

You can solve it by adding the following part to android/app/build.gradle,

implementation(project(':react-native-maps')){
exclude group: 'com.google.android.gms'
}
implementation 'com.google.android.gms:play-services-base:12.0.1'
implementation 'com.google.android.gms:play-services-basement:12.0.1'
implementation 'com.google.android.gms:play-services-location:12.0.1'
implementation 'com.google.android.gms:play-services-tasks:12.0.1'
implementation 'com.google.android.gms:play-services-maps:12.0.1'

that is supposed to solve the problem.

Another problem you may face is to end up with a blank map, the first thing you need to do is to check whether your emulator has Google Play Services or not. Do that by going to tools then SDK Maneger from android studio and from there check whether Google Play Services is installed or not, if not make sure to install it.

If the problem still not solved create a new virtual device “emulator” and let it be the latest version of oreo. That should solve the problem.

By this sentence I finish this article with all my wishes that you find what you wanted to learn from this article.

*Links of the previous parts :

--

--

Hasan Dader

Seasoned Senior React Native Developer. Passionate about innovation and delivering high-quality mobile solutions.