React-native-maps: No current position button under Android

Created on 8 Feb 2017  路  63Comments  路  Source: react-native-maps/react-native-maps

Hi,
I'm a newbie in React Native and trying to create a simple app with maps. I created the default empty project, imported and linked the react-native-maps. I have the following code in my index.android.js:

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View
} from 'react-native';
import MapView from 'react-native-maps';

export default class TestProject2 extends Component {
    render() {
        return (
            <View style={styles.container}>
                <MapView style={styles.map}
                    provider="google"
                    showsUserLocation={true}
                    showsMyLocationButton={true}
                    showsCompass={true}
                    followsUserLocation={true}
                    loadingEnabled={true}
                    toolbarEnabled={true}
                    zoomEnabled={true}
                    rotateEnabled={true}

                    initialRegion={{
                        latitude: 37.78825,
                        longitude: -122.4324,
                        latitudeDelta: 0.0922,
                        longitudeDelta: 0.0421,
                    }}
                />
            </View>
        );
    };
}

const styles = StyleSheet.create({
    container: {
        ...StyleSheet.absoluteFillObject,
        flex: 1,
        justifyContent: 'center',
        alignItems: 'stretch',
        backgroundColor: 'gray',
    },
    map: {
        ...StyleSheet.absoluteFillObject
    },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

AppRegistry.registerComponent('TestProject2', () => TestProject2);

What I expect: at least the current location button should be shown. But I get the empty map without any buttons at startup:

198

If i change orientation of the device the current location button appears:

199

Screenshots are made from the api_19 emulator. Api_23 has the same issue. Buildtools 23.0.2.

What I do wrong and how to make buttons visible at startup?
Thanks.

stale

Most helpful comment

A simple fix (inspired by psam44) that works perfectly for me is to force a re-render after the initial presentation of the map.
In my case, I'm changing _paddingTop_ with one pixel "after some time" (after the map is drawn initially)

//Setting a value (padding) we can change to force a repaint 
 <View style={{paddingTop: this.state.statusBarHeight }}>
        <MapView .......
              .......

Note: the initial value of statusBarHeight is set to one pixel less, than what is used below:

componentWillMount() {
     //Hack to ensure the showsMyLocationButton is shown initially. Idea is to force a repaint
    setTimeout(()=>this.setState({statusBarHeight: Constants.statusBarHeight}),500);
   .....

On my (android) device this works like a dream (read, you dont notice this repaint ;-)

All 63 comments

I'm having this issue as well...

Me too

When I put a marker in a map the button shows on start as normal

I have the same issue..

self closing map case this problem with marker inside problem resolved for me

I'm having this issue too

I'm having this issue too, but on iOS

any news on this ? i'm having the exact same problem
after changing orientation back to vertical the button remains there.
What's supposed to happen when you click the my location button ?

First of all, a reminder: for the button to be visible, some criteria are needed (OK in the OP):

  • showsUserLocation must be set true because the default is false
  • showsMyLocationButton must stay true, as is the default

The issue is in a race condition between two asynchronous events: onMapReady and onLayout
(noted Ready and Layout, for short, below).
The decision to draw the button or not is done at the time of the Layout, and the setting of UserLocation can't be done before the Ready of the map (a handle to it is needed).
Unfortunately, usually, at launch, the Layout arrives before the Ready.

Notation:

  • the log messages are mine (so not in the original source code)
  • MV is MapView.js
  • AMV is AirMapView.java
 I/ReactNativeJS: MV const
 I/ReactNativeJS: MV render !isReady
 I/System.out: AMV const
 I/System.out: setRegion null
 I/System.out: onLayout true 0 0 666 466  <--- First
 I/ReactNativeJS: MV _onLayout
 I/System.out: onMapReady  <--- Second
 I/ReactNativeJS: MV render !isReady
 I/System.out: onHostResume, showUserLocation: false
 I/ReactNativeJS: MV _onMapReady
 I/ReactNativeJS: MV render isReady
 I/System.out: setRegion { NativeMap:... }
 I/System.out: setShowUserLocation true   <--- too late !

In real native development, we may use something like invalidate() or requestLayout() on the map view to ask for its redraw. But these methods are not effective in the RN framework.

How comes that the button may appear sometimes?
It can be the case on a "Reload" menu action: as some things are already loaded, Ready is able to fired quicker and before the Layout.
I don't know why, I can't reproduce this case now. Here is an old trace:

AMV const
onMapReady   <--- First
onHostResume, showUserLocation: false
onAttachedToWindow   <--- Second ; the onLayout is very near after this
showUserLocation true

I don't know how to force a refresh of the layout. The only way seems to change the style.
Here is a (bad) workaround: for the layout before Ready, distort a property, and restore it after the Ready.
The property must have an impact on the layout. For example zIndex or any padding are not suitable.
I found that a margin is a candidate.

Changes are in node_modulesreact-native-maps\components\MapView.js
in render(), change:
style: this.props.style,
by:
style: {...this.props.style, marginLeft: 1},

in _onMapReady(), add:
this.map.setNativeProps({ style: {...this.props.style, marginLeft: 0} });

Note that with this writing, you can style your component with StyleSheet.absoluteFillObject but not StyleSheet.absoluteFill (kind of immutable).

Traces are now:

I/ReactNativeJS: MV const
I/ReactNativeJS: MV render !isReady
I/System.out: AMV const
I/System.out: setRegion null
I/System.out: onMapReady
I/System.out: onHostResume, showUserLocation: false
I/System.out: onAttachedToWindow
I/System.out: AMV updateExtraData {height=703.0, width=1278.6687}
I/System.out: onMeasure 1073743103 1073742527
I/System.out: onSizeChanged 1279 703 0 0
I/System.out: onLayout true 1 0 1280 703   <--- first layout
I/ReactNativeJS: MV _onMapReady
I/ReactNativeJS: MV render isReady
I/ReactNativeJS: MV _onLayout
I/System.out: setRegion { NativeMap: ... }  <-- 1st Region
I/System.out: setShowUserLocation true  <-- too late for the first but ok for the second
I/System.out: onMeasure 1073743104 1073742527
I/System.out: onSizeChanged 1280 703 1279 703
I/System.out: onLayout true 0 0 1280 703  <--- second layout, with the button
I/ReactNativeJS: MV _onLayout
I/System.out: setRegion { NativeMap: ... }  <-- 2nd Region
I/ReactNativeJS: MV _onChange
I/ReactNativeJS: MV _onChange

Note something weird: there are two identical setRegion().
The first call is coming from _onMapReady() (no condition), the other one from the second _onLayout() (subject to the Ready condition, and called only one time).
Why two copies? A possible explanation is that _onMapReady is only available on Android, so the work has to be done at _onLayout for the iOS case.
This duplicate is revealed by the workaround but it is already there in the original code with only one expected Layout : think of the case where the Ready could fully arrive before the Layout.

Why are there two _onChange? Probably because of the two setRegion, each one in a different layout context.

As said before, this code change should not be considered as a fix, but as a step in the understanding.

Any solutions to this yet? Rather how does AirBNB handle this? Or how are other people getting along with this?

I solved the problem by creating my own button. If this helps anyone feel free to use my solution.
It is important to set the zIndex of MapView to -1 so that buttons show up.
I dynamically grab the height of the screen so I can place the button on the bottom.
I control the region using state, and the method _findMe().
Below:
```
import {
TouchableOpacity,
Dimensions,
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
import React, { Component } from 'react';
import MapView from 'react-native-maps'

export default class TheMap extends Component {
constructor(props){
super(props);
}

_findMe(){
navigator.geolocation.getCurrentPosition(
({coords}) => {
const {latitude, longitude} = coords
this.setState({
position: {
latitude,
longitude,
},
region: {
latitude,
longitude,
latitudeDelta: 0.005,
longitudeDelta: 0.001,
}
})
},
(error) => alert(JSON.stringify(error)),
{enableHighAccuracy: true, timeout: 20000, maximumAge: 1000}
)
}
componentDidMount() {
navigator.geolocation.getCurrentPosition(
({coords}) => {
const {latitude, longitude} = coords
this.setState({
position: {
latitude,
longitude,
},
region: {
latitude,
longitude,
latitudeDelta: 0.005,
longitudeDelta: 0.001,
}
})
},
(error) => alert('Error: Are location services on?'),
{enableHighAccuracy: true}
);
this.watchID = navigator.geolocation.watchPosition(
({coords}) => {
const {lat, long} = coords
this.setState({
position: {
lat,
long
}
})
});
}
componentWillUnmount() {
navigator.geolocation.clearWatch(this.watchID);
}
render() {
const { height: windowHeight } = Dimensions.get('window');
const varTop = windowHeight - 125;
const hitSlop = {
top: 15,
bottom: 15,
left: 15,
right: 15,
}
bbStyle = function(vheight) {
return {
position: 'absolute',
top: vheight,
left: 10,
right: 10,
backgroundColor: 'transparent',
alignItems: 'center',
}
}
return(
hitSlop = {hitSlop}
activeOpacity={0.7}
style={styles.mapButton}
onPress={ () => this._findMe() }
>
Find Me



style={styles.map}
region={this.state.region}
showsUserLocation={true}
>


);
}
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#EEEEEE',
},
text: {
color: 'white',
},
map: {
flex: 1,
zIndex: -1,
},
mapButton: {
width: 75,
height: 75,
borderRadius: 85/2,
backgroundColor: 'rgba(252, 253, 253, 0.9)',
justifyContent: 'center',
alignItems: 'center',
shadowColor: 'black',
shadowRadius: 8,
shadowOpacity: 0.12,
opacity: .6,
zIndex: 10,
},
AppRegistry.registerComponent('TheMap', () => TheMap);


Hey this issue seems a little dead at the moment. Anyone familiar with the android part to do a PR or explain how to fix it?

Drawing our own button is an alternative but it does function differently as it goes through the navigator interface and and sets the region through the region prop. Things do feel snappier using the Google Map SDK's own button.

A simple fix (inspired by psam44) that works perfectly for me is to force a re-render after the initial presentation of the map.
In my case, I'm changing _paddingTop_ with one pixel "after some time" (after the map is drawn initially)

//Setting a value (padding) we can change to force a repaint 
 <View style={{paddingTop: this.state.statusBarHeight }}>
        <MapView .......
              .......

Note: the initial value of statusBarHeight is set to one pixel less, than what is used below:

componentWillMount() {
     //Hack to ensure the showsMyLocationButton is shown initially. Idea is to force a repaint
    setTimeout(()=>this.setState({statusBarHeight: Constants.statusBarHeight}),500);
   .....

On my (android) device this works like a dream (read, you dont notice this repaint ;-)

@Lars-m this is life saving. Thank you.

@Lars-m Thank you! I chose paddingButtom for 1 pixel in 500ms, and change back to 0 pixel in 1000ms :)

@Lars-m Thank you! Great solution.

@Lars-m getting error cannot find variable constant
can you help

sidd123456, Constantis a nice convenience class defined by Expo.

If' you don't use Expo, or don't need to adjust for the size of the statusBarHeight, the idea is just to change one value (margin, padding) that will force the re-render.
You can even get rid of most of the code from my original example if you just use reacts forceUpdate(), method to trigger the re-render. Some will probably argue that the use of this method is discouraged by Facebook, but I would claim that here, it really makes sense.
I haven't tested this, but I'm pretty sure this single line (added to componentWillMount) will do the job:

componentWillMount() {
    setTimeout(()=>this.forceUpdate() (), 500);
   ..

@Lars-m thanks a lot.it worked

@Lars-m, while this workaround worked almost perfectly on Testing (I'm using Expo as well but it didn't show the button 100% times) now that I've built the apk and installed it on the same device I used on Testing, it doesn't show anymore...

It's incredibly weird how Airbnb doesn't fix this issue...

@Lars-m nice one - the padding worked for me but not the forceUpdate()

@Lars-m it doesnt work ios any way to make it work

@Lars-m in my case it was the marginTop switch between 1px (initial) to 0px that worked.
Works also on actual android 6.0 device

any update here ?

Anything new on this?

I upgraded to latest version, and I am having the same issue, any update on this ?

Adding provider="google" made it work for me.

@ewein don't add provider="google" but use the correct constant exported by the module.
If in the future the module will change that constant for some reason it won't work anymore ;)

Yeah, ended up just making my own button:

  1. Add ref to map view
<MapView
     ref={component => {this._map = component;}}
     showsUserLocation={true}
     showsMyLocationButton={false}
     initialRegion={this.state.region}
     onRegionChange={this._onRegionChange}
     style={styles.mapView}
>
  1. On button press animate to current location using animateToRegion
    _animateToCurrentLocation = async () => {
        const userLocation = await Location.getCurrentPositionAsync({
            enableHighAccuracy: true
        });

        this._map.animateToRegion({
            latitude: userLocation.coords.latitude,
            longitude: userLocation.coords.longitude,
            latitudeDelta: 0.005,
            longitudeDelta: 0.005
        }, 2000);
    };

facing the same problem on rn .49 Android, ForceUpdate or adding provider also doesnt help.

For android 6.0 and above, MapView needs to be rendered after obtaining Runtime Permission.

PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION)
.then(granted => {
    this.forceUpdate();
});

i use this and work weel for me

<Mapview
  style={[ screenStyle.map , { width: this.state.width }]}
  onMapReady={() => this.setState({ width: width - 1 })}
/>

I've tried to setState for re-render. But it doesn't work.
Any helps?
Thansk in advance.

This is a default functionality why we can't make it works?

The re-render workaround only works for me when the
cacheEnabled={false}
is input to <MapView>

The re-render workaround using padding property didn't work for me. I was able to make it work using the following approach:

  constructor(props) {
    super(props);
    this.state = {flex: 0};
  }
  componentDidMount(){
    setTimeout(()=>this.setState({flex: 1}),500);
  }



md5-889bd7136f9371194a36aed14994f973



<MapView
        provider="google"
        style={{flex: this.state.flex}}
        showsMyLocationButton={true}
        showsUserLocation={true}
        followsUserLocation={true}
      />

Is there any way to fix this from native code? How can I invalidate google map view? I've tried both invalidate and requestLayout, and they don't fix this issue. I don't like the workaround with size or margin updates, this is too slow and visible for user.
I do need this to make mapPadding work.

I tries using the flex, padding or margin solutions and none work for me. Until I set a width for the map.

const { width, height } = Dimensions.get('window')

const styles = StyleSheet.create({
  container: {
    ...StyleSheet.absoluteFillObject,
    justifyContent: 'flex-end',
    alignItems: 'center',
  },
  map: {
    // ...StyleSheet.absoluteFillObject,
    width: width,
  }
})

componentDidMount(){
    setTimeout(()=>this.setState({flex: 1}),100);
  }

  render () {
    return (
      <View style={styles.container}>
        <MapView
          style={[styles.map, {flex: this.state.flex}]}
          showsUserLocation={true}
          showsMyLocationButton={true}
          provider={MapView.PROVIDER_GOOGLE}
          region={{
            latitude: 37.78825,
            longitude: -122.4324,
            latitudeDelta: 0.015,
            longitudeDelta: 0.0121,
          }}
        >
        </MapView>
      </View>
    )
  }

Tested on iOS and Android (Emulator, S7 Edge and Moto G5)

I had no choice but to create my own button. I tried the other solutions that changed the layout but it didn't work for me. I hope this problem gets fixed soon.

I get this problem too. Toolbar and buttons do not show up - meaning that the majority of the UI is missing, which is a huge problem.

More than that, I can't even add my own button over it - even to CLOSE the map when it's within a modal. Moving it to another screen didn't fix anything except being able to close/leave the screen.

darkmantle, you should be able to add your own button. Did you set the zindex of the button to negative?

Yes. It works fine on iOS, just not Android. But the UI is still missing.

Tried @Lars-m 's solution, works sometimes but most of the time it doesn't and I'm getting this warning which seems to be connected.
image
Here's the guts of my Map component, which is part of a child of a parent that contains others.

type Props = {
  user: Object,
  lastKnownLocation: Object,
  nearbyLocations: Array<Object>,
  onMapReady: Function,
  navigation: Object,
  locationEnabled: boolean
};

type State = {
  initialized: boolean,
  statusBarHeight: number
};

export class Map extends Component<Props, State> {
  constructor (props: Object) {
    super(props)
    this.state = {
      statusBarHeight: 1,
      initialized: false
    }
  }

  onMapReady = () => {
    this.setState({ initialized: true })
    this.setState({statusBarHeight: statusBarHeightConstant})
  }
  // Gets the coords properties of lastKnownLocation, returning default coordinates if none are available
  render () {
    let coords = _.get(this.props.lastKnownLocation, 'activity.coords', {latitude: 51.5033640, longitude: -0.1276250})
    return (
      <MapView
        provider={PROVIDER_GOOGLE}
        style={styles.mapStyle}
        cacheEnabled={false}
        customMapStyle = {RetroMapStyles}
        region={{
          latitude: coords.latitude,
          longitude: coords.longitude,
          latitudeDelta: 0.0009,
          longitudeDelta: 0.0009
        }}
        onMapReady = {this.onMapReady}
        showsUserLocation = {this.props.locationEnabled}
        showsMyLocationButton = {true}
        zoomEnabled = {true}
        scrollEnabled = {false}
        moveOnMarkerPress = {true}
        zoomControlEnabled = {true}
        onPress={() => this.props.navigation.setParams({swipeEnabled: false})}>
        {this.props.venuesAndSubcategories.map(venueAndSubcategory => (
          <Marker
            key = { venueAndSubcategory.venue.id }
            tracksViewChanges = { !this.state.initialized }
            coordinate =
              {{latitude: venueAndSubcategory.venue.location.lat,
                longitude: venueAndSubcategory.venue.location.lng }}>
            <Image source = {CategoryImages[venueAndSubcategory.subcategory.categoryKey] } style={styles.mapMarkerStyle} />
          </Marker>
        ))}
      </MapView>
    )
  }
}

const mapStateToProps = (state: ReduxState) => {
  return {
    lastKnownLocation: state.locations.lastKnownLocation,
    numNewLocTriggered: state.locations.numNewLocTriggered,
    allAwardedLocations: state.locations.allAwardedLocations,
    nearbyLocations: state.locations.nearbyLocations,
    venuesAndSubcategories: state.locations.venuesAndSubcategories,
    user: state.user,
    locationEnabled: state.locations.locationEnabled
  }
}

export default connect(
  mapStateToProps
)(Map)

@aperhaite solution worked for me.

@aperhaite 's solution also worked for me. I had my MapView wrapped in a parent component which represented the entire screen that contained it. It was working sporadically when the logic was contained in the Map component itself, but after I moved it up a level it worked every time. Also one thing that I found confusing when implementing this re-rendering method was whether or not my style needed to have the parameter that is being manipulated in the _first place_. It seems like it doesn't.
i.e. if you're manipulating the flex property your style doesn't need to have the flex property.

@aperhaite Thanks

@aperhaite OMG, It works for me!! Thanks a lot!!

@ewein 's solution worked for me. Made an own button component work with a react native init project like this:

async moveToRegion() {
        await navigator.geolocation.getCurrentPosition(
            (geoLocation) => {
            this.map.animateToRegion({
                latitude: geoLocation.coords.latitude,
                longitude: geoLocation.coords.longitude,
                latitudeDelta: 0.005,
                longitudeDelta: 0.005
            }, 2000);
        });
    },
<View style={styles.map_container}>
  <MapView
     ref={ref => { this.map = ref; }}
     onRegionChange={this.onRegionChange}
     onRegionChangeComplete={(res) => this.onRegionChangeComplete(res)}
     region={region}
     showsUserLocation
     showsMyLocationButton={false}
     style={styles.map}
  />
  <MyLocationButton 
      action={() => this.moveToRegion()}
  />
</ View>

Linked to #2010

On EXPO when you turn off and back on the screen it actually loads the button.

is there a real solution for this yet that does not involve geolocation or re-rendering the mapveiw?

@Lars-m non off the work around are showing the userlocation button nor the user location for me!? any idea on how to do this in 2019? Thanks!

Is this pkg being maintained?

I used this and worked for me

<Mapview
  style={[ screenStyle.map , { width: this.state.width }]}
  onMapReady={() => this.setState({ width: width - 1 })}
/>

Thank you

wow this is a thing since 2017. But I think the make our own button solution seem pretty good!

I fix it by "shaking" the map a little bit. How? A little bit rudimentary:

componentWillMount() {
    this.state.mapMargin = 1;
    this.setState({mapMargin: 1})
  }  

  componentDidMount() {
    if (Platform.OS === 'android') {
      setTimeout( () => {
        this.state.mapMargin = 0;
        this.setState({mapMargin: 0})
      }, 100);
    }
  }

I use the map in full, with style

height: Dimensions.get('window').height,
width: Dimensions.get('window').width,

A simple fix (inspired by psam44) that works perfectly for me is to force a re-render after the initial presentation of the map.
In my case, I'm changing _paddingTop_ with one pixel "after some time" (after the map is drawn initially)

//Setting a value (padding) we can change to force a repaint 
 <View style={{paddingTop: this.state.statusBarHeight }}>
        <MapView .......
              .......

Note: the initial value of statusBarHeight is set to one pixel less, than what is used below:

componentWillMount() {
     //Hack to ensure the showsMyLocationButton is shown initially. Idea is to force a repaint
    setTimeout(()=>this.setState({statusBarHeight: Constants.statusBarHeight}),500);
   .....

On my (android) device this works like a dream (read, you dont notice this repaint ;-)

You are a lifesaver

I use the re-render solution as well. However, I use it at the Mapviews "OnMapReady" - Event, which calls a function that updates a styling object in my state. The styling object sets the Mapview Style. IE:

style={this.state.customMapStyle}
zoomEnabled={true}
loadingEnabled={true}
showsUserLocation={true}
showsMyLocationButton={true}
zoomControlEnabled={true}
onMapReady={() => {this.updateMapStyling();}}
onUserLocationChange={(coordinates) => {this.userLocationChanged(coordinates);}}
>


updateMapStyling(){
this.setState({customMapStyle: {flex: 1}});
}

Here is a fully working code in react hooks

import React, { useState, useEffect } from 'react'
import { StyleSheet, View, PermissionsAndroid } from 'react-native'
import MapView, { PROVIDER_GOOGLE } from 'react-native-maps'

const App = () => {

  const [mapWidth, setMapWidth] = useState('99%')

  // Update map style to force a re-render to make sure the geolocation button appears
  const updateMapStyle = () => {
    setMapWidth('100%')
  }

  // Request geolocation in Android since it's not done automatically
  const requestGeoLocationPermission = () => {
    PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION)
  }

  return (
    <View style={styles.container}>

      <MapView
        provider={PROVIDER_GOOGLE}
        mapType='standard'
        customMapStyle={googleMapStyle}
        style={[styles.map, { width: mapWidth }]}
        showsUserLocation={true}
        showsMyLocationButton={true}
        showsCompass={true}
        onMapReady={() => {
          requestGeoLocationPermission()
          updateMapStyle()
        }}
      >

      </MapView>
    </View>
  )
}

const googleMapStyle = [{
  featureType: "administrative",
  elementType: "geometry",
  stylers: [{
    visibility: "off"
  }]
}]

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center'
  },
  map: {
    height: '100%'
  }
})

export default App

Hi I get the solution here , just add customMapStyle
https://github.com/react-native-community/react-native-maps/issues/1033#issuecomment-587050175

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

AzarouAmine picture AzarouAmine  路  52Comments

skylarmb picture skylarmb  路  52Comments

vinceyuan picture vinceyuan  路  61Comments

priithaamer picture priithaamer  路  53Comments

ghost picture ghost  路  197Comments