React-native-карты и геолокация неправильно определяют местоположение

#android #ios #react-native #geolocation #react-native-maps

#Android #iOS #react-native #геолокация #react-native-карты

Вопрос:

Я новичок в изучении React Native и был бы признателен за любую помощь, которая может быть предоставлена с моим кодом. В настоящее время я работаю над учебными пособиями и застрял на несколько часов. Надеюсь, этой информации достаточно, чтобы получить некоторую помощь. Заранее спасибо!

Моя цель

Я пытаюсь сделать три вещи (в таком порядке):

  1. Получить текущее местоположение пользователя
  2. Извлеките данные элементов из API (пример приведен ниже). Следует отметить, что в конечном итоге содержимое выборки будет зависеть от текущего местоположения пользователя.
  3. Проанализируйте данные элементов, чтобы создать маркеры и уникальный список категорий, и разместите их на карте с текущим местоположением пользователя в центре для начала.

Я хочу иметь возможность отслеживать местоположение пользователя и соответствующим образом перемещать и обновлять карту. Я также не хочу показывать карту, пока все маркеры не будут на месте.

Вот мои версии react: react-native-cli: 2.0.1 react-native: 0.63.2

Мои ошибки

Я использую как эмулятор Android Studio, так и эмулятор Xcode и в настоящее время сталкиваюсь со следующими проблемами:

  1. Эмулятор iOS в Xcode в первый раз отображается нормально, но при последующих обновлениях я вижу, что 1 или два из моих 5 маркеров отсутствуют.
  2. На Android карта загружается идеально, затем появляется, чтобы сразу перерисовать себя и сосредоточиться на Googleplex в Калифорнии вместо текущего местоположения пользователя. Местоположение эмулятора установлено на Лондон, Великобритания

Я подозреваю, что у меня может быть какое-то состояние гонки с выборкой элементов и текущего местоположения до отображения карты, но я не могу быть уверен.

Мой код

// структура ответа моего API

 {
"id": "96845",
"title": "Item_title_goes_here",
"image": "https://someURL/image.JPG",
"stories": 46,
"lat": some_lat_number,
"lon": some_lon_number,
"category": "category_name",
"description": "long_description"
},
  
 //ajax.js 

export default {
    async fetchInitialItems() {
        try {
            const response = await fetch(apiHost   '/api/v1/items/');
            const responseJson = await response.json();
            return responseJson;
        } catch (error) {
            console.error(error);
        }
    },
    async fetchItemDetail(itemId) {
        try {
            const response = await fetch(apiHost   '/api/items/'   itemId);
            const responseJson = await response.json();
            return responseJson;
        } catch (error) {
            console.error(error);
        }
    },
};
  
 //ExploreScreen.js (my map component)

import React, { Component } from 'react';
import {
    View,
    Text,
    StyleSheet,
    Image,
    Animated,
    Dimensions,
    TouchableOpacity,
    PermissionsAndroid,
    ScrollView,
    Platform,
    StatusBar,
} from 'react-native';
import MapView, {
    PROVIDER_GOOGLE,
    Marker,
    Callout,
    Polygon,
} from 'react-native-maps';
import PropTypes from 'prop-types';
import Geolocation from '@react-native-community/geolocation';
import { mapDarkStyle, mapStandardStyle } from '../model/mapData';
import ajax from '../utils/ajax';
import MapCarousel from './MapCarousel';

import Ionicons from 'react-native-vector-icons/Ionicons';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import Fontisto from 'react-native-vector-icons/Fontisto';

import StarRating from '../components/StarRating';

/**
|--------------------------------------------------
| Variables
|--------------------------------------------------
*/
const { width, height } = Dimensions.get('window');

const SCREEN_HEIGHT = height;
const SCREEN_WIDTH = width;
const ASPECT_RATIO = width / height;
const LATITUDE_DELTA = 0.0422;
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;
const darkTheme = false;


/**
|--------------------------------------------------
| Component
|--------------------------------------------------
*/
class ExploreScreen extends React.Component {
    /****** Props amp; States ******/

    state = {
        region: {
            latitude: 0,
            longitude: 0,
            latitudeDelta: LATITUDE_DELTA,
            longitudeDelta: LONGITUDE_DELTA,
        },
        items: [],
        markers: [],
        categories: [],
        currentMapRegion: null,
    };

    /****** Functions ******/

    ///when carousel item selected
    onCarouselItemSelected() {
        alert('carousel item selected');
    }

    //when the carousel in scrolled
    onCarouselIndexChange(itemID) {
        const item = this.state.items.find(item => item.id == itemID);
        const marker = this.state.markers.find(marker => marker.id == itemID);
        const coordinates = { lat: item.lat, lon: item.lon };

        this.goToLocation(coordinates);
    }

    //get current position
    getLocation(that) {
        Geolocation.getCurrentPosition(
            //get the current location
            position => {
                const region = {
                    latitude: parseFloat(position.coords.latitude),
                    longitude: parseFloat(position.coords.longitude),
                    latitudeDelta: LATITUDE_DELTA,
                    longitudeDelta: LONGITUDE_DELTA,
                };
                that.setState({ region });
            },
            error => alert(error.message),
            { enableHighAccuracy: true, timeout: 20000 },
        );
        //get location on location change
        that.watchID = Geolocation.watchPosition(position => {
            const currentRegion = {
                latitude: position.coords.latitude,
                longitude: position.coords.longitude,
                latitudeDelta: LATITUDE_DELTA,
                longitudeDelta: LONGITUDE_DELTA,
            };
            this.setState({ region: currentRegion });
        });
    }
    //move map to a lat/lon
    goToLocation(coordinates) {
        if (this.map) {
            this.map.animateToRegion({
                latitude: coordinates.lat,
                longitude: coordinates.lon,
                latitudeDelta: LATITUDE_DELTA,
                longitudeDelta: LONGITUDE_DELTA,
            });
        }
    }

    //when the region changes for the map
    onRegionChangeComplete(region) {
        //I dont know what to do here 
    }

    //move map to center of current location
    gotToCenter() {
        if (this.map) {
            this.map.animateToRegion({
                latitude: this.state.region.latitude,
                longitude: this.state.region.longitude,
                latitudeDelta: LATITUDE_DELTA,
                longitudeDelta: LONGITUDE_DELTA,
            });
        }
    }
    //map the categories store in the state
    mapCategories = () => {
        const uniqueCategories = [];
        this.state.items.map(item => {
            if (uniqueCategories.indexOf(item.category) === -1) {
                uniqueCategories.push(item.category);
            }
        });
        this.setState({ categories: uniqueCategories });
    };

    //map the items to markers and store in the state
    mapMarkers = () => {
        const markers = this.state.items.map(item => (
            <Marker
                key={item.id}
                coordinate={{ latitude: item.lat, longitude: item.lon }}
                image={require('../assets/map_marker.png')}
                tracksViewChanges={false}
                title={item.title}
                description={item.description}
            />
        ));
        this.setState({ markers: markers });
    };

    /****** Lifecycle Functions ******/
    async componentDidMount() {
        var that = this;
        //Checking for the permission just after component loaded
        if (Platform.OS === 'ios') {
            //for ios
            this.getLocation(that);
        } else {
            //for android
            async function requestLocationPermission() {
                try {
                    const granted = await PermissionsAndroid.request(
                        PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
                        {
                            title: 'Location Access Required',
                            message: 'This App needs to Access your location',
                        },
                    );
                    if (granted === PermissionsAndroid.RESULTS.GRANTED) {
                        //To Check, If Permission is granted
                        that.getLocation(that);
                    } else {
                        alert('Permission Denied');
                    }
                } catch (err) {
                    alert('err', err);
                    console.warn(err);
                }
            }
            requestLocationPermission();
        }

        //get location on location change
        that.watchID = Geolocation.watchPosition(position => {
            const currentRegion = {
                latitude: parseFloat(position.coords.latitude),
                longitude: parseFloat(position.coords.longitude),
                latitudeDelta: LATITUDE_DELTA,
                longitudeDelta: LONGITUDE_DELTA,
            };

            this.setState({ region: currentRegion });
        });

        console.log(this.state.region);

        const items = await ajax.fetchInitialItems();
        this.setState({ items });
        this.mapMarkers();
        this.mapCategories();
    }

    componentWillUnmount = () => {
        Geolocation.clearWatch(this.watchID);
    };

    render() {
        const lat = this.state.region.latitude;
        const lon = this.state.region.longitude;

        if (this.state.items amp;amp; lat amp;amp; lon) {
            return (
                <View>
                    <MapView
                        ref={map => {
                            this.map = map;
                        }}
                        onRegionChangeComplete={this.onRegionChangeComplete.bind(this)}
                        toolbarEnabled={false}
                        showsMyLocationButton={false}
                        provider={PROVIDER_GOOGLE}
                        style={styles.map}
                        customMapStyle={darkTheme ? mapDarkStyle : mapStandardStyle}
                        showsUserLocation={true}
                        followsUserLocation={true}
                        region={this.state.region}>
                        {this.state.markers}
                    </MapView>
                    {/* center button */}
                    <TouchableOpacity
                        onPress={this.gotToCenter.bind(this)}
                        style={styles.centerButtonContainer}>
                        <Ionicons name="md-locate" size={20} />
                    </TouchableOpacity>
                    <View style={styles.carousel}>
                        <MapCarousel
                            data={this.state.items}
                            onPressItem={() => {
                                this.onCarouselItemSelected.bind(this);
                            }}
                            onUpdateLocation={this.onCarouselIndexChange.bind(this)}
                        />
                    </View>
                </View>
            );
        } else {
            return (
                <View style={styles.container}>
                    <Text style={styles.header}>Loading...</Text>
                </View>
            );
        }
    }
}

/**
|--------------------------------------------------
| Styles
|--------------------------------------------------
*/
const styles = StyleSheet.create({
    container: {
        ...StyleSheet.absoluteFillObject,
        height: '100%',
        width: '100%',
        justifyContent: 'center',
        alignItems: 'center',
    },
    map: {
        height: '100%',
    },
    carousel: {
        position: 'absolute',
        bottom: 25,
    },
    header: {
        fontSize: 50,
    },
    //character name
    name: {
        fontSize: 15,
        marginBottom: 5,
    },
    //character image
    image: {
        width: 170,
        height: 80,
    },
    //center button
    centerButtonContainer: {
        width: 40,
        height: 40,
        position: 'absolute',
        bottom: 260,
        right: 10,
        borderColor: '#191919',
        borderWidth: 0,
        borderRadius: 30,
        backgroundColor: '#d2d2d2',
        justifyContent: 'center',
        alignItems: 'center',
        shadowColor: '#000',
        shadowOffset: {
            width: 0,
            height: 9,
        },
        shadowOpacity: 0.48,
        shadowRadius: 11.95,
        elevation: 18,
        opacity: 0.9,
    },
});

export default ExploreScreen;
  

Комментарии:

1. Сначала немного очистите код, у вас есть две точки наблюдения (вам действительно нужна эта функция?). Проблема в ios может заключаться в том, что вы устанавливаете элементы setState, но вы не думаете об этом, что вызов является асинхронным, поэтому, когда вы запускаете mapMarkers state.items может быть пустым, точно так же в вашем состоянии рендеринга.items всегда присутствует, потому что это [], поэтому, когда lat и lon есть, это будет рендеринг. Также вы можете удалить onRegionChangeComplete. Обновите меня результатами и изменениями.

2. Я не уверен, как решить проблему асинхронности. Делаю ли я вызовы синхронными? Если да, то каким образом?

3. Вы можете просто передавать элементы напрямую, если this.state.items равно null.

4. Я не уверен, как поступить здесь. Я сделал эти вызовы синхронными, но сейчас я ничего не вижу на карте.

5. что вы получили, когда mapMarkers отображается в консоли, регистрируйте this.state.items и в консоли метода визуализации. запишите это.состояние.маркеры