Статьи

Отслеживание местоположения в реальном времени с React Native и PubNub

С постоянно растущим использованием мобильных приложений, функции геолокации и отслеживания можно найти в большинстве приложений. Отслеживание геолокации в режиме реального времени играет важную роль во многих службах по запросу, таких как эти:

  • услуги такси, такие как Uber, Lyft или Ola
  • Услуги доставки еды, такие как Uber Eats, Foodpanda или Zomato
  • мониторинг флотов дронов

В этом руководстве мы собираемся использовать React Native для создания приложения для отслеживания местоположения в реальном времени. Мы создадим два приложения React Native. Одно будет работать как приложение для отслеживания (называемое «приложение для отслеживания»), а другое будет отслеживаемым («приложение для отслеживания»).

Вот как будет выглядеть окончательный результат этого урока:

Хотите узнать React Native с нуля? Эта статья является выдержкой из нашей Премиум библиотеки. Получите полную коллекцию книг React Native, охватывающую основы, проекты, советы и инструменты и многое другое с SitePoint Premium. Присоединяйтесь сейчас всего за $ 9 / месяц .

Предпосылки

Это руководство требует базовых знаний о React Native. Чтобы настроить машину для разработки, следуйте официальному руководству здесь .

Помимо React Native, мы также будем использовать PubNub , сторонний сервис, который обеспечивает передачу и обновление данных в режиме реального времени. Мы будем использовать этот сервис для обновления пользовательских координат в режиме реального времени.

Зарегистрируйте бесплатную учетную запись PubNub здесь .

Поскольку мы будем использовать Карты Google на Android, нам также потребуется ключ API Карт Google, который вы можете получить на странице ключа API Google Maps Get .

Чтобы убедиться, что мы находимся на одной странице, вот версии, используемые в этом руководстве:

  • Узел v10.15.0
  • нпм 6.4.1
  • пряжа 1.16.0
  • реактивный род 0,59,9
  • response-native-maps 0.24.2
  • Пубнуб-Реакция 1.2.0

Начиная

Если вы хотите прямо сейчас взглянуть на исходный код наших приложений Tracker и Trackee, вот их ссылки на GitHub:

Давайте начнем с приложения Trackee в первую очередь.

Приложение Trackee

Чтобы создать новый проект с использованиемact react-native-cli , введите его в терминале:

 $ react-native init trackeeApp 
 $ cd trackeeApp 

Теперь давайте перейдем к самой интересной части — кодированию.

Добавить React Native Maps

Поскольку мы будем использовать Карты в нашем приложении, нам понадобится библиотека для этого. Мы будем использовать ответные нативные карты .

Установите react-native-maps , следуя инструкциям по установке здесь .

Добавить PubNub

Помимо карт мы также установим SDK PubNub React для передачи наших данных в режиме реального времени:

 $ yarn add pubnub-react 

После этого вы можете запустить приложение:

 $ react-native run-ios $ react-native run-android 

Вы должны увидеть что-то вроде этого на вашем симуляторе / эмуляторе:

Приложение Trackee

Код треки

Теперь откройте файл App.js выполните следующие App.js импорта:

 import React from "react"; import { StyleSheet, View, Platform, Dimensions, SafeAreaView } from "react-native"; import MapView, { Marker, AnimatedRegion } from "react-native-maps"; import PubNubReact from "pubnub-react"; 

Помимо MapView, который будет отображать карту в нашем компоненте, мы импортировали Marker и AnimatedRegion из react-native-mas .

Marker идентифицирует местоположение на карте. Мы будем использовать его для определения местоположения пользователя на карте.

AnimatedRegion позволяет нам использовать Animated API для управления центром карты и масштабированием.

После импорта необходимого компонента мы определим некоторые константы и начальные значения для наших карт:

 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; 

Затем мы определим наш компонент класса с помощью некоторых состояний, методов жизненного цикла и пользовательских вспомогательных методов:

 export default class App extends React.Component { constructor(props) { super(props); this.state = { latitude: LATITUDE, longitude: LONGITUDE, coordinate: new AnimatedRegion({ latitude: LATITUDE, longitude: LONGITUDE, latitudeDelta: 0, longitudeDelta: 0 }) }; this.pubnub = new PubNubReact({ publishKey: "X", subscribeKey: "X" }); this.pubnub.init(this); } componentDidMount() { this.watchLocation(); } componentDidUpdate(prevProps, prevState) { if (this.props.latitude !== prevState.latitude) { this.pubnub.publish({ message: { latitude: this.state.latitude, longitude: this.state.longitude }, channel: "location" }); } } componentWillUnmount() { navigator.geolocation.clearWatch(this.watchID); } watchLocation = () => { const { coordinate } = this.state; this.watchID = navigator.geolocation.watchPosition( position => { const { latitude, longitude } = position.coords; const newCoordinate = { latitude, longitude }; if (Platform.OS === "android") { if (this.marker) { this.marker._component.animateMarkerToCoordinate( newCoordinate, 500 // 500 is the duration to animate the marker ); } } else { coordinate.timing(newCoordinate).start(); } this.setState({ latitude, longitude }); }, error => console.log(error), { enableHighAccuracy: true, timeout: 20000, maximumAge: 1000, distanceFilter: 10 } ); }; getMapRegion = () => ({ latitude: this.state.latitude, longitude: this.state.longitude, latitudeDelta: LATITUDE_DELTA, longitudeDelta: LONGITUDE_DELTA }); render() { return ( <SafeAreaView style={{ flex: 1 }}> <View style={styles.container}> <MapView style={styles.map} showUserLocation followUserLocation loadingEnabled region={this.getMapRegion()} > <Marker.Animated ref={marker => { this.marker = marker; }} coordinate={this.state.coordinate} /> </MapView> </View> </SafeAreaView> ); } } const styles = StyleSheet.create({ container: { ...StyleSheet.absoluteFillObject, justifyContent: "flex-end", alignItems: "center" }, map: { ...StyleSheet.absoluteFillObject } }); 

Уф! Это много кода, поэтому давайте пройдемся по нему постепенно.

Во-первых, мы инициализировали некоторое локальное состояние в нашем constructor() . Мы также инициализируем экземпляр PubNub:

 constructor(props) { super(props); this.state = { latitude: LATITUDE, longitude: LONGITUDE, coordinate: new AnimatedRegion({ latitude: LATITUDE, longitude: LONGITUDE, latitudeDelta: 0, longitudeDelta: 0, }), }; // Initialize PubNub this.pubnub = new PubNubReact({ publishKey: 'X', subscribeKey: 'X', }); this.pubnub.init(this); } 

Вам нужно заменить «X» на собственные ключи публикации и подписки PubNub. Чтобы получить ключи, войдите в свою учетную запись PubNub и перейдите на панель инструментов.

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

Скопируйте и вставьте ключи в экземпляр конструктора PubNub.

После этого мы будем использовать жизненный цикл componentDidMount() для вызова метода watchLocation :

 componentDidMount() { this.watchLocation(); } watchLocation = () => { const { coordinate } = this.state; this.watchID = navigator.geolocation.watchPosition( position => { const { latitude, longitude } = position.coords; const newCoordinate = { latitude, longitude, }; if (Platform.OS === 'android') { if (this.marker) { this.marker._component.animateMarkerToCoordinate(newCoordinate, 500); // 500 is the duration to animate the marker } } else { coordinate.timing(newCoordinate).start(); } this.setState({ latitude, longitude, }); }, error => console.log(error), { enableHighAccuracy: true, timeout: 20000, maximumAge: 1000, distanceFilter: 10, } ); }; 

watchLocation использует API geolocation для наблюдения за изменениями координат местоположения пользователя. Поэтому каждый раз, когда пользователь перемещается и его координаты положения изменяются, watchPosition будет возвращать новые координаты пользователя.

watchPosition принимает два параметра — options и callback .

В качестве опции мы установим для enableHighAccuracy значение true для высокой точности, а для distanceInterval10 чтобы получать обновления только в том случае, если местоположение изменилось как минимум на десять метров. Если вы хотите максимальной точности, используйте 0 , но имейте в виду, что он будет использовать больше пропускной способности и данных.

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

 const { latitude, longitude } = position.coords; this.setState({ latitude, longitude }); 

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

Для этого мы будем использовать animateMarkerToCoordinate() для Android animateMarkerToCoordinate() coordinate.timing() для iOS. Мы передадим объект newCoordinate с latitude и longitude в качестве параметра этим методам:

 if (Platform.OS === "android") { if (this.marker) { this.marker._component.animateMarkerToCoordinate(newCoordinate, 500); // 500 is the duration to animate the marker } } else { coordinate.timing(newCoordinate).start(); } 

Мы также хотим, чтобы координаты пользователя постоянно отправлялись в наше приложение Tracker. Для этого мы будем использовать метод жизненного цикла React’s componentDidUpdate :

  componentDidUpdate(prevProps, prevState) { if (this.props.latitude !== prevState.latitude) { this.pubnub.publish({ message: { latitude: this.state.latitude, longitude: this.state.longitude, }, channel: 'location', }); } } 

Компонент componentDidUpdate вызывается сразу после обновления. Поэтому он будет вызываться каждый раз, когда меняются координаты пользователя.

Кроме того, мы использовали условие if для публикации координат только при изменении широты.

Затем мы вызвали метод PubNub publish для публикации координат вместе с названием канала, в котором мы хотим опубликовать эти координаты.

Примечание: убедитесь, что название channel одинаково в обоих приложениях. В противном случае вы не получите никаких данных.

Теперь, когда мы закончили со всеми необходимыми методами, давайте отрендерим наш MapView . Добавьте этот код в ваш метод render :

 return ( <SafeAreaView style={{ flex: 1 }}> <View style={styles.container}> <MapView style={styles.map} showUserLocation followUserLocation loadingEnabled region={this.getMapRegion()} > <Marker.Animated ref={marker => { this.marker = marker; }} coordinate={this.state.coordinate} /> </MapView> </View> </SafeAreaView> ); 

Мы использовали Marker.Animated , который будет перемещаться анимированным образом по мере перемещения пользователей и изменения их координат.

 componentWillUnmount() { navigator.geolocation.clearWatch(this.watchID); } 

Мы также очистим все методы наблюдения geolocation в componentWillUnmount() чтобы избежать любых утечек памяти.

Давайте закончим приложение Trackee, добавив несколько стилей:

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

Поскольку мы хотим, чтобы наша карта занимала весь экран, мы должны использовать абсолютное позиционирование и устанавливать каждую сторону на ноль ( position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 ).

StyleSheet предоставляет absoluteFill который можно использовать для удобства и уменьшения дублирования этих повторяющихся стилей.

Запуск приложения Trackee

Прежде чем идти дальше, всегда полезно проверить наше приложение. Мы можем сделать это, выполнив следующие шаги.

На iOS

Если вы используете симулятор iOS, вам повезло. Это очень легко проверить эту функцию в iOS по сравнению с Android.

В настройках симулятора iOS выберите « Отладка» > « Расположение» > « Автострада» и обновите приложение ( Cmd + R ). Вы должны увидеть что-то вроде этого:

Приложение Trackee

На андроид

К сожалению, для Android нет простого способа тестирования этой функции.

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

Вы также можете использовать Genymotion , которая имеет утилиту для моделирования местоположения.

Тестирование на PubNub

Чтобы проверить, получает ли PubNub данные, вы можете включить аналитику в реальном времени, которая покажет количество сообщений, которые ваше приложение получает или отправляет.

На вкладке « Ключи » перейдите в нижнюю часть и включите аналитику в реальном времени . Затем перейдите в Real-time Analytics, чтобы проверить, принимаются ли данные.

Это все, что нужно приложению Trackee, так что давайте перейдем к приложению Tracker.

Приложение Tracker

Выполните те же действия, что и для приложения Trackee, и создайте новый проект React Native под названием trackerApp .

Как приложения Tracker, так и приложения Trackee разделяют большинство своего кода.

Разница лишь в том, что в trackerApp мы будем получать координаты местоположения из trackeeApp через PubNub.

Добавьте SDK pubnub-react , импортируйте и инициализируйте, как мы делали в приложении Trackee.

В componentDidMount() добавьте следующее:

 // same imports as trackeeApp componentDidMount() { /* remove watchLocation = () => {} */ // add: this.subscribeToPubNub(); } // add: subscribeToPubNub = () => { this.pubnub.subscribe({ channels: ['location'], withPresence: true, }); this.pubnub.getMessage('location', msg => { const { coordinate } = this.state; const { latitude, longitude } = msg.message; const newCoordinate = { latitude, longitude }; if (Platform.OS === 'android') { if (this.marker) { this.marker._component.animateMarkerToCoordinate(newCoordinate, 500); } } else { coordinate.timing(newCoordinate).start(); } this.setState({ latitude, longitude, }); }); }; /* remove watchLocation = () => { } */ 

Вот пробная версия обновленного кода для приложения Tracker.

В приведенном выше коде мы используем метод subscribe PubNub для подписки на наш канал определения location как только компонент будет смонтирован.

После этого мы используем getMessage для получения сообщений, полученных по этому каналу.

Мы будем использовать эти координаты для обновления MapView приложения Tracker.

Поскольку оба приложения имеют одинаковый набор координат, мы должны видеть координаты приложения Trackee в приложении Tracker.

Запуск обоих приложений вместе

Наконец мы на последнем этапе. Непросто протестировать оба приложения на одном компьютере в режиме разработки.

Чтобы протестировать оба приложения на компьютере с iOS, я собираюсь выполнить следующие действия:

  1. Мы собираемся запустить приложение Trackee на симуляторе iOS, так как оно имеет режим отладки, в котором я могу имитировать движущееся транспортное средство. Я также собираюсь запустить его в режиме релиза, поскольку у нас не может быть запущено два пакета одновременно:

      $ react-native run-ios --configuration Release 

    Теперь перейдите в Debug > Location > Freeway Drive .

  2. Мы запустим приложение Tracker на эмуляторе Android:

      $ react-native run-android 

Приложение Tracker теперь должно иметь возможность отслеживать перемещение Marker как в приложении Trackee.

Вы можете найти исходный код для обоих приложений на GitHub.

Вывод

Это просто базовая реализация сервисов отслеживания местоположения в реальном времени. Мы просто царапаем поверхность тем, чего можем достичь с помощью отслеживания местоположения. На самом деле, возможности безграничны. Например:

  • Вы могли бы создать сервис езды на лошадях, как Uber, Lyft и т. Д.
  • Используя отслеживание местоположения, вы можете отслеживать ваши заказы, например, продукты питания или продукты от местного продавца.
  • Вы можете отслеживать местоположение ваших детей (полезно для родителей или учителей).
  • Вы можете отслеживать животных в охраняемом национальном парке.

Если вы используете это для создания собственной реализации отслеживания местоположения, я бы хотел увидеть результаты. Дайте мне знать в Твиттере .