Environment
“axios”: “^0.18.0”,
“expo”: “^26.0.0”,
“expo-cli”: “^2.1.3”,
“global”: “^4.3.2”,
“jwt-decode”: “^2.2.0”,
“lodash”: “^4.17.10”,
“moment-timezone”: “^0.5.23”,
“native-base”: “^2.5.2”,
“react”: “16.3.0-alpha.1”,
“react-native”: “0.54.0”,
“react-native-animatable”: “^1.3.0”,
“react-native-maps”: “^0.21.0”,
Steps to Reproduce
When the component loads and create the markers after that the expo stop and ask to reopen the application.
Expected Behavior
The right behavior is show the markers and the map fit in then.
Actual Behavior
The application stop just for android platform and gives this error in adb logcat
12-03 08:14:23.564 3588 3588 D AndroidRuntime: Shutting down VM
12-03 08:14:24.134 3588 3588 E AndroidRuntime: FATAL EXCEPTION: main
12-03 08:14:24.134 3588 3588 E AndroidRuntime: Process: host.exp.exponent, PID: 3588
12-03 08:14:24.134 3588 3588 E AndroidRuntime: java.lang.RuntimeException: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child’s parent first.
12-03 08:14:24.134 3588 3588 E AndroidRuntime: at abi26_0_0.com.facebook.react.bridge.ReactContext.handleException(ReactContext.java:313)
12-03 08:14:24.134 3588 3588 E AndroidRuntime: at abi26_0_0.com.facebook.react.uimanager.GuardedFrameCallback.doFrame(GuardedFrameCallback.java:33)
12-03 08:14:24.134 3588 3588 E AndroidRuntime: at abi26_0_0.com.facebook.react.modules.core.ReactChoreographer$ReactChoreographerDispatcher.doFrame(ReactChoreographer.java:136)
12-03 08:14:24.134 3588 3588 E AndroidRuntime: at abi26_0_0.com.facebook.react.modules.core.ChoreographerCompat$FrameCallback$1.doFrame(ChoreographerCompat.java:107)
Reproducible Demo
Here is the code of this component.
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
ActivityIndicator,
AsyncStorage,
Dimensions,
Platform,
Image,
ScrollView
} from 'react-native';
import MapView, {
Marker,
Callout,
AnimatedRegion,
Animated
} from 'react-native-maps';
import {
Container,
Content,
Right,
Header,
Body,
Button,
Item,
Input,
Card,
CardItem,
Icon
} from 'native-base';
import _ from 'lodash';
import moment from 'moment-timezone';
import SocketIOClient from 'socket.io-client';
import jwtDecode from 'jwt-decode';
import axios from 'axios';
const { width, height } = Dimensions.get('window');
const ASPECT_RATIO = width / height;
const LATITUDE_DELTA = 0.004757;
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;
class Home extends Component {
mapMarkers = [];
constructor(props) {
super(props);
this.state = {
markers: [],
isLoaging: true
};
this.exibirMarcadores = this.exibirMarcadores.bind(this);
this.centralizarMarker = this.centralizarMarker.bind(this);
}
componentWillMount() {
this.carregarVeiculos(true);
}
carregarVeiculos = fitToElements => {
this.setState({ isLoaging: true });
AsyncStorage.getItem('token').then(token => {
let dados = jwtDecode(token);
axios
.get('api/home', {/
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: 'Bearer ' + token
}
})
.then(res => {
//alert(JSON.stringify(res.data));
this.setState({ markers: res.data, isLoaging: false });
if (fitToElements == true) {
this.map.fitToElements(false);
}
/*Platform.select({
ios: this.map.fitToElements(true)
});*/
});
});
};
componentDidMount() {
}
zoom(latitude, longitude) {
let newRegion = {
latitude: parseFloat(latitude),
longitude: parseFloat(longitude),
latitudeDelta: parseFloat(LATITUDE_DELTA),
longitudeDelta: parseFloat(LONGITUDE_DELTA)
};
this.map.animateToRegion(newRegion, 1000);
}
centralizarMarker(veiculo) {
let ids = [];
ids.push(veiculo.id);
this.setState({
filtrados: null
});
Platform.select({
ios: this.map.fitToSuppliedMarkers(ids)
});
this.mapMarkers[veiculo.id].showCallout();
this.zoom(veiculo.latitude, veiculo.longitude);
}
carregarIcone(icone) {
let path = null;
switch (icone) {
case 'carro.png':
path = require('../../images/icones/carro.png');
break;
case 'ambulancia.png':
path = require('../../images/icones/ambulancia.png');
break;
case 'caminhao.png':
path = require('../../images/icones/caminhao.png');
break;
case 'caminhonete.png':
path = require('../../images/icones/caminhonete.png');
break;
case 'carregadeira.png':
path = require('../../images/icones/carregadeira.png');
break;
case 'empilhadeira.png':
path = require('../../images/icones/empilhadeira.png');
break;
case 'escavadeira.png':
path = require('../../images/icones/escavadeira.png');
break;
case 'moto.png':
path = require('../../images/icones/moto.png');
break;
case 'munk.png':
path = require('../../images/icones/munk.png');
break;
case 'onibus.png':
path = require('../../images/icones/onibus.png');
break;
case 'van.png':
path = require('../../images/icones/van.png');
break;
default:
path = require('../../images/icones/carro.png');
break;
}
return path;
}
exibirMarcadores() {
if (this.state.markers.length > 0) {
let markers = this.state.markers.map(marker => {
if (marker.latitude && marker.longitude) {
let pathIcone = this.carregarIcone(marker.icone);
return (
<Marker
key={marker.id}
identifier={marker.id}
image={pathIcone}
identifier={marker.id.toString()}
ref={ref => {
this.mapMarkers[marker.id.toString()] = ref;
}}
coordinate={
new AnimatedRegion({
latitude: parseFloat(marker.latitude),
longitude: parseFloat(marker.longitude)
})
}
>
<Callout>
<View>
<Right />
{Platform.OS === 'ios' ? (
<Button
light
style={{ width: 50, borderRadius: 10 }}
onPress={() => {
this.mapMarkers[marker.id].hideCallout();
}}
>
<Text>Fechar</Text>
</Button>
) : (
<View />
)}
<Text style={{ fontWeight: 'bold', fontSize: 18 }}>
Informações do Veículo {marker.placa}
</Text>
<Text>
<Text style={{ fontWeight: 'bold' }}>Placa:</Text>
{marker.placa}
</Text>
<Text>
<Text style={{ fontWeight: 'bold' }}>
Data Recebimento:
</Text>
{moment
.utc(marker.data_recebimento)
.tz('America/Sao_Paulo')
.format('DD/MM/YYYY HH:mm:ss')}
</Text>
<Text style={{ fontWeight: 'bold' }}>Motorista:</Text>
<Text>
<Text style={{ fontWeight: 'bold' }}>Localização:</Text>
{marker.referencia}
</Text>
<Text>
<Text style={{ fontWeight: 'bold' }}>Ignição:</Text>
{marker.ignicao == '1' ? (
<Icon name="key" style={{ color: 'green' }} />
) : (
<Icon name="key" style={{ color: 'black' }} />
)}
</Text>
<Text>
<Text style={{ fontWeight: 'bold' }}>Sinal GPS:</Text>
{marker.sinalGPS > 0 ? (
<Icon name="wifi" style={{ color: 'green' }} />
) : (
<Icon name="wifi" style={{ color: 'black' }} />
)}
</Text>
<Text>
<Text style={{ fontWeight: 'bold' }}>Hodometro: </Text>
{marker.hodometro.split('.')[0]} Km e{' '}
{marker.hodometro.split('.')[1]} m
</Text>
<Text>
<Text style={{ fontWeight: 'bold' }}>Horimetro:</Text>
{(marker.horimetro / 60).toFixed(0)} hrs e{' '}
{marker.horimetro % 60} min
</Text>
</View>
</Callout>
</Marker>
);
}
});
return markers;
}
}
pesquisar(valor) {
if (valor != '') {
let filtrados = this.state.markers.filter(veiculo => {
if (
veiculo.placa.toLowerCase().indexOf(valor.toLowerCase()) !== -1 ||
veiculo.descricao.toLowerCase().indexOf(valor.toLowerCase()) !== -1
) {
return true;
} else {
return false;
}
});
let list = filtrados.map((veiculo, index) => {
return (
<CardItem
style={{ flexGrow: 1, borderColor: 'black' }}
button
bordered
onPress={() => {
this.centralizarMarker(veiculo);
}}
>
<Image
source={this.carregarIcone(veiculo.icone)}
style={{ padding: 5, margin: 5 }}
/>
<View style={styles.cardItem}>
<Text style={{ fontWeight: 'bold', fontSize: 14 }}>
{veiculo.placa}
</Text>
<Text style={{ fontSize: 12 }}>{veiculo.descricao} </Text>
</View>
</CardItem>
);
});
this.setState({
filtrados: (
<Card style={styles.searchList}>
<ScrollView>{list}</ScrollView>
</Card>
)
});
} else {
this.setState({
filtrados: null
});
}
}
render() {
const { width, height } = Dimensions.get('window');
const ratio = width / height;
return (
<Container>
<Header iosBarStyle="light-content" style={styles.header}>
<Body>
<Item>
<Input
style={{ color: 'white' }}
onChangeText={valor => this.pesquisar(valor)}
placeholderTextColor="white"
placeholder="Pesquisar"
/>
</Item>
</Body>
</Header>
<Content scrollEnabled={false}>
<View style={{ width, height }}>
{this.state.filtrados}
<MapView
showsUserLocation={true}
userLocationAnnotationTitle="Minha Localização"
showsMyLocationButton={true}
style={styles.map}
zoomEnabled={true}
fitToElements={true}
//provider="google"
ref={ref => {
this.map = ref;
}}
loadingEnabled={this.state.isLoaging}
>
<Button
iconLeft
white
light
style={styles.atualizar}
disabled={this.state.isLoaging}
onPress={() => {
this.carregarVeiculos(false);
}}
>
<Icon name="refresh" />
</Button>
{this.exibirMarcadores()}
</MapView>
</View>
</Content>
</Container>
);
}
}
export default Home;
const styles = StyleSheet.create({
container: {
flex: 1,
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
justifyContent: 'flex-end',
alignItems: 'center'
},
header: {
backgroundColor: '#325b84',
...Platform.select({
android: {
marginTop: 25
}
})
},
cardItem: {
...Platform.select({
ios: {
justifyContent: 'left'
}
})
},
map: {
marginTop: 1.5,
...StyleSheet.absoluteFillObject
},
atualizar: {
width: 45,
borderRadius: 40,
color: '#53b2e0',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.8,
shadowRadius: 2,
position: 'absolute',
bottom: 150,
right: 10
},
searchList: {
zIndex: 2
}
});