-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #814 from airbnb/gil/BugMarkerWontUpdate
[android] Create example for marker not updating bug
- Loading branch information
Showing
4 changed files
with
316 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import React from 'react'; | ||
import { | ||
StyleSheet, | ||
View, | ||
Text, | ||
Dimensions, | ||
TouchableOpacity, | ||
} from 'react-native'; | ||
import MapView from 'react-native-maps'; | ||
import MyLocationMapMarker from './MyLocationMapMarker'; | ||
|
||
const { width, height } = Dimensions.get('window'); | ||
|
||
const ASPECT_RATIO = width / height; | ||
const LATITUDE = 37.78825; | ||
const LONGITUDE = -122.4324; | ||
const LATITUDE_DELTA = 0.0922; | ||
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO; | ||
|
||
class BugMarkerWontUpdate extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
|
||
this.state = { | ||
region: { | ||
latitude: LATITUDE, | ||
longitude: LONGITUDE, | ||
latitudeDelta: LATITUDE_DELTA, | ||
longitudeDelta: LONGITUDE_DELTA, | ||
}, | ||
coordinate: { | ||
latitude: LATITUDE, | ||
longitude: LONGITUDE, | ||
}, | ||
amount: 0, | ||
enableHack: false, | ||
}; | ||
} | ||
|
||
increment() { | ||
this.setState({ amount: this.state.amount + 10 }); | ||
} | ||
|
||
decrement() { | ||
this.setState({ amount: this.state.amount - 10 }); | ||
} | ||
|
||
toggleHack() { | ||
this.setState({ enableHack: !this.state.enableHack }); | ||
} | ||
|
||
render() { | ||
return ( | ||
<View style={styles.container}> | ||
<MapView | ||
provider={this.props.provider} | ||
style={styles.map} | ||
initialRegion={this.state.region} | ||
> | ||
<MyLocationMapMarker | ||
coordinate={this.state.coordinate} | ||
heading={this.state.amount} | ||
enableHack={this.state.enableHack} | ||
/> | ||
</MapView> | ||
<View style={styles.buttonContainer}> | ||
<TouchableOpacity | ||
onPress={() => this.toggleHack()} | ||
style={[styles.bubble, styles.button, styles.hackButton]} | ||
> | ||
<Text style={{ fontSize: 12, fontWeight: 'bold' }}> | ||
{this.state.enableHack ? 'Disable Hack' : 'Enable Hack'} | ||
</Text> | ||
</TouchableOpacity> | ||
</View> | ||
<View style={styles.buttonContainer}> | ||
<TouchableOpacity | ||
onPress={() => this.decrement()} | ||
style={[styles.bubble, styles.button]} | ||
> | ||
<Text style={{ fontSize: 20, fontWeight: 'bold' }}>-</Text> | ||
</TouchableOpacity> | ||
<TouchableOpacity | ||
onPress={() => this.increment()} | ||
style={[styles.bubble, styles.button]} | ||
> | ||
<Text style={{ fontSize: 20, fontWeight: 'bold' }}>+</Text> | ||
</TouchableOpacity> | ||
</View> | ||
</View> | ||
); | ||
} | ||
} | ||
|
||
BugMarkerWontUpdate.propTypes = { | ||
provider: MapView.ProviderPropType, | ||
}; | ||
|
||
const styles = StyleSheet.create({ | ||
container: { | ||
...StyleSheet.absoluteFillObject, | ||
justifyContent: 'flex-end', | ||
alignItems: 'center', | ||
}, | ||
map: { | ||
...StyleSheet.absoluteFillObject, | ||
}, | ||
bubble: { | ||
backgroundColor: 'rgba(255,255,255,0.7)', | ||
paddingHorizontal: 18, | ||
paddingVertical: 12, | ||
borderRadius: 20, | ||
}, | ||
latlng: { | ||
width: 200, | ||
alignItems: 'stretch', | ||
}, | ||
button: { | ||
width: 80, | ||
paddingHorizontal: 12, | ||
alignItems: 'center', | ||
marginHorizontal: 10, | ||
}, | ||
hackButton: { | ||
width: 200, | ||
}, | ||
buttonContainer: { | ||
flexDirection: 'row', | ||
marginVertical: 20, | ||
backgroundColor: 'transparent', | ||
}, | ||
}); | ||
|
||
module.exports = BugMarkerWontUpdate; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
import React, { PropTypes } from 'react'; | ||
import { | ||
StyleSheet, | ||
Text, | ||
View, | ||
PermissionsAndroid, | ||
Platform, | ||
} from 'react-native'; | ||
import MapView from 'react-native-maps'; | ||
import isEqual from 'lodash/isEqual'; | ||
|
||
const GEOLOCATION_OPTIONS = { enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 }; | ||
const ANCHOR = { x: 0.5, y: 0.5 }; | ||
|
||
const colorOfmyLocationMapMarker = 'blue'; | ||
|
||
const propTypes = { | ||
...MapView.Marker.propTypes, | ||
// override this prop to make it optional | ||
coordinate: PropTypes.shape({ | ||
latitude: PropTypes.number.isRequired, | ||
longitude: PropTypes.number.isRequired, | ||
}), | ||
children: PropTypes.node, | ||
geolocationOptions: PropTypes.shape({ | ||
enableHighAccuracy: PropTypes.bool, | ||
timeout: PropTypes.number, | ||
maximumAge: PropTypes.number, | ||
}), | ||
heading: PropTypes.number, | ||
enableHack: PropTypes.bool, | ||
}; | ||
|
||
const defaultProps = { | ||
enableHack: false, | ||
geolocationOptions: GEOLOCATION_OPTIONS, | ||
}; | ||
|
||
export default class MyLocationMapMarker extends React.PureComponent { | ||
constructor(props) { | ||
super(props); | ||
this.mounted = false; | ||
this.state = { | ||
myPosition: null, | ||
}; | ||
} | ||
componentDidMount() { | ||
this.mounted = true; | ||
// If you supply a coordinate prop, we won't try to track location automatically | ||
if (this.props.coordinate) return; | ||
|
||
if (Platform.OS === 'android') { | ||
PermissionsAndroid.requestPermission(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION) | ||
.then(granted => { | ||
if (granted && this.mounted) this.watchLocation(); | ||
}); | ||
} else { | ||
this.watchLocation(); | ||
} | ||
} | ||
watchLocation() { | ||
// eslint-disable-next-line no-undef | ||
this.watchID = navigator.geolocation.watchPosition((position) => { | ||
const myLastPosition = this.state.myPosition; | ||
const myPosition = position.coords; | ||
if (!isEqual(myPosition, myLastPosition)) { | ||
this.setState({ myPosition }); | ||
} | ||
}, null, this.props.geolocationOptions); | ||
} | ||
componentWillUnmount() { | ||
this.mounted = false; | ||
// eslint-disable-next-line no-undef | ||
if (this.watchID) navigator.geolocation.clearWatch(this.watchID); | ||
} | ||
render() { | ||
let { heading, coordinate } = this.props; | ||
if (!coordinate) { | ||
const { myPosition } = this.state; | ||
if (!myPosition) return null; | ||
coordinate = myPosition; | ||
heading = myPosition.heading; | ||
} | ||
|
||
const rotate = (typeof heading === 'number' && heading >= 0) ? `${heading}deg` : null; | ||
|
||
return ( | ||
<MapView.Marker | ||
anchor={ANCHOR} | ||
style={styles.mapMarker} | ||
{...this.props} | ||
coordinate={coordinate} | ||
> | ||
<View style={styles.container}> | ||
<View style={styles.markerHalo} /> | ||
{rotate && | ||
<View style={[styles.heading, { transform: [{ rotate }] }]}> | ||
<View style={styles.headingPointer} /> | ||
</View> | ||
} | ||
<View style={styles.marker}> | ||
<Text style={{ width: 0, height: 0 }}> | ||
{this.props.enableHack && rotate} | ||
</Text> | ||
</View> | ||
</View> | ||
{this.props.children} | ||
</MapView.Marker> | ||
); | ||
} | ||
} | ||
|
||
const SIZE = 35; | ||
const HALO_RADIUS = 6; | ||
const ARROW_SIZE = 7; | ||
const ARROW_DISTANCE = 6; | ||
const HALO_SIZE = SIZE + HALO_RADIUS; | ||
const HEADING_BOX_SIZE = HALO_SIZE + ARROW_SIZE + ARROW_DISTANCE; | ||
|
||
const styles = StyleSheet.create({ | ||
mapMarker: { | ||
zIndex: 1000, | ||
}, | ||
// The container is necessary to protect the markerHalo shadow from clipping | ||
container: { | ||
width: HEADING_BOX_SIZE, | ||
height: HEADING_BOX_SIZE, | ||
}, | ||
heading: { | ||
position: 'absolute', | ||
top: 0, | ||
left: 0, | ||
width: HEADING_BOX_SIZE, | ||
height: HEADING_BOX_SIZE, | ||
alignItems: 'center', | ||
}, | ||
headingPointer: { | ||
width: 0, | ||
height: 0, | ||
backgroundColor: 'transparent', | ||
borderStyle: 'solid', | ||
borderTopWidth: 0, | ||
borderRightWidth: ARROW_SIZE * 0.75, | ||
borderBottomWidth: ARROW_SIZE, | ||
borderLeftWidth: ARROW_SIZE * 0.75, | ||
borderTopColor: 'transparent', | ||
borderRightColor: 'transparent', | ||
borderBottomColor: colorOfmyLocationMapMarker, | ||
borderLeftColor: 'transparent', | ||
}, | ||
markerHalo: { | ||
position: 'absolute', | ||
backgroundColor: 'white', | ||
top: 0, | ||
left: 0, | ||
width: HALO_SIZE, | ||
height: HALO_SIZE, | ||
borderRadius: Math.ceil(HALO_SIZE / 2), | ||
margin: (HEADING_BOX_SIZE - HALO_SIZE) / 2, | ||
shadowColor: 'black', | ||
shadowOpacity: 0.25, | ||
shadowRadius: 2, | ||
shadowOffset: { | ||
height: 0, | ||
width: 0, | ||
}, | ||
}, | ||
marker: { | ||
justifyContent: 'center', | ||
backgroundColor: colorOfmyLocationMapMarker, | ||
width: SIZE, | ||
height: SIZE, | ||
borderRadius: Math.ceil(SIZE / 2), | ||
margin: (HEADING_BOX_SIZE - SIZE) / 2, | ||
}, | ||
}); | ||
|
||
MyLocationMapMarker.propTypes = propTypes; | ||
MyLocationMapMarker.defaultProps = defaultProps; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters