Skip to content

Commit

Permalink
Merge pull request #814 from airbnb/gil/BugMarkerWontUpdate
Browse files Browse the repository at this point in the history
[android] Create example for marker not updating bug
  • Loading branch information
lelandrichardson committed Mar 26, 2017
2 parents d795302 + 571fbb5 commit 4e8326f
Show file tree
Hide file tree
Showing 4 changed files with 316 additions and 0 deletions.
2 changes: 2 additions & 0 deletions example/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import MapStyle from './examples/MapStyle';
import LegalLabel from './examples/LegalLabel';
import SetNativePropsOverlays from './examples/SetNativePropsOverlays';
import CustomOverlay from './examples/CustomOverlay';
import BugMarkerWontUpdate from './examples/BugMarkerWontUpdate';

const IOS = Platform.OS === 'ios';
const ANDROID = Platform.OS === 'android';
Expand Down Expand Up @@ -148,6 +149,7 @@ class App extends React.Component {
[LegalLabel, 'Reposition the legal label', true],
[SetNativePropsOverlays, 'Update native props', true],
[CustomOverlay, 'Custom Overlay Component', true],
[BugMarkerWontUpdate, 'BUG: Marker Won\'t Update (Android)', true],
]
// Filter out examples that are not yet supported for Google Maps on iOS.
.filter(example => ANDROID || (IOS && (example[2] || !this.state.useGoogleMaps)))
Expand Down
134 changes: 134 additions & 0 deletions example/examples/BugMarkerWontUpdate.js
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;
179 changes: 179 additions & 0 deletions example/examples/MyLocationMapMarker.js
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;
1 change: 1 addition & 0 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"dev": "concurrently 'npm run watch' 'npm run packager'"
},
"dependencies": {
"lodash": "^4.17.2",
"react": "~15.4.1",
"react-native": "^0.40.0",
"react-native-maps": "file:../"
Expand Down

0 comments on commit 4e8326f

Please sign in to comment.