Статьи

Отсоединение Экспо-Приложений от ЭкспоКита

В моей статье «Упрощенная разработка React Native с помощью Expo» вы узнали о том, как Expo облегчает начинающим создавать приложения с помощью React Native. Вы также узнали, что Expo позволяет разработчикам быстрее приступить к разработке приложений React Native, поскольку больше не нужно настраивать Android Studio, Xcode или другие инструменты разработки.

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

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

  • Отключение приложений Expo Expo от ExpoKit: основные понятия

  • Проще реагировать на развитие с помощью Expo

Чтобы отключиться от ExpoKit, сначала необходимо отредактировать файлы app.json и package.json .

В файле app.json убедитесь, что name установлено. platforms должны быть платформами, которые вы хотите построить.

1
2
3
4
5
6
7
{
 «expo»: {
   «name»: «ocdmom»,
   «platforms»: [
     «ios»,
     «android»
   ],

Если вы хотите собрать для iOS, вы должны указать опцию ios :

1
2
3
«ios»: {
  «bundleIdentifier»: «com.ocd.mom»
},

Если вы хотите поддерживать Android, то также укажите следующую опцию:

1
2
3
«android»: {
  «package»: «com.ocd.mom»
}

Есть и другие параметры, которые были предварительно заполнены инструментом командной строки exp при создании проекта. Но единственными важными являются bundleIdentifier для iOS и package для Android. Это будут уникальные идентификаторы приложения после их публикации в магазине Apple или Play. Отсоединение требует этих подробностей, потому что оно фактически генерирует собственный код для приложения, которое будет запущено на устройстве. Вы можете найти больше информации о различных параметрах конфигурации для файла app.json в документации Expo .

Вы можете просмотреть полное содержимое файла в репозитории GitHub .

Далее откройте файл package.json и добавьте название проекта:

1
«name»: «ocdmom»

Это должно быть имя, которое вы использовали при создании проекта с использованием exp init . Очень важно, чтобы они были одинаковыми, потому что name , указанное в package.json , используется при компиляции приложения. Несоответствия в этом имени приведут к ошибке.

Теперь мы готовы отсоединиться от ExpoKit. Выполните следующую команду в корне каталога проекта:

1
exp detach

Это позволит загрузить собственные пакеты Экспо для Android и iOS локально.

Вы должны увидеть вывод, похожий на следующий, если это удалось:

Экспо отряд

Если вы развертываете на iOS, вам необходимо установить последнюю версию Xcode. На момент написания этого руководства последняя версия — 9. Затем установите CocoaPods, выполнив sudo gem install cocoapods . Это позволяет вам установить родные iOS-зависимости проекта. После этого перейдите в каталог ios проекта и выполните pod install чтобы установить все собственные зависимости.

Теперь, когда мы отсоединились, теперь мы можем установить нативные пакеты, как в стандартном проекте React Native.

Для этого приложения нам понадобятся пакеты React Native Background Timer и Pusher .

Во-первых, установите пакет Pusher, потому что это проще:

1
npm install —save pusher-js

Это позволяет нам общаться с приложением Pusher, которое вы создали ранее.

Затем установите React Native Background Timer. Это позволяет нам периодически выполнять код (даже если приложение находится в фоновом режиме) на основе определенного интервала:

1
npm install —save react-native-background-timer

В отличие от пакета Pusher, для этого требуется привязать нативную библиотеку (iOS или Android) к приложению. Выполнение следующей команды сделает это за вас:

1
react-native link

Как только это будет сделано, он должен также инициализировать модуль в android / app / src / main / host / exp / exponent / MainApplication.java . Но просто чтобы убедиться, проверьте, существует ли следующее в этом файле:

1
2
3
4
5
6
7
import com.ocetnik.timer.BackgroundTimerPackage;
 
public List<ReactPackage> getPackages() {
    return Arrays.<ReactPackage>asList(
      new BackgroundTimerPackage() // also check this
    );
}

Если вы создаете для iOS, откройте Podfile в каталоге ios и убедитесь, что перед объявлением post_install добавлено post_install :

1
pod ‘react-native-background-timer’, :path => ‘../node_modules/react-native-background-timer’

Как только это будет сделано, выполните pod install в каталоге ios чтобы установить собственный модуль.

Для Android это уже делается автоматически при запуске приложения с помощью Android Studio.

Если вы создаете для Android, откройте файл манифеста Android ( android / app / src / main / AndroidManifest.xml ) и убедитесь, что добавлены следующие разрешения:

1
2
3
4
<uses-permission android:name=»android.permission.ACCESS_NETWORK_STATE» />
<uses-permission android:name=»android.permission.INTERNET» />
<uses-permission android:name=»android.permission.ACCESS_COARSE_LOCATION» />
<uses-permission android:name=»android.permission.ACCESS_FINE_LOCATION» />

Это позволяет нам запрашивать разрешение у Pusher на доступ к Интернету и Expo, чтобы узнать текущее местоположение пользователя на устройствах Android.

Мы еще не закончили, но лучше запустить приложение сейчас, чтобы вы уже могли видеть, работает оно или нет. Таким образом, вы также можете увидеть изменения во время разработки приложения.

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

Если вы создаете для Android, откройте Android Studio и выберите Открыть существующий проект Android Studio . В появившемся селекторе каталогов выберите папку android внутри проекта Expo. После того, как вы выбрали папку, она должна проиндексировать файлы в этой папке. На этом этапе вы сможете перестроить проект, выбрав « Build> Rebuild Project» в верхнем меню. После этого запустите приложение, выбрав « Выполнить»> «Запустить приложение» .

Android Studio может запускать приложение на любом устройстве Android, подключенном к вашему компьютеру, на одном из эмуляторов, которые вы установили через Android Studio или через Genymotion (Android Studio автоматически обнаруживает запущенный экземпляр эмулятора). Для этого приложения я рекомендую использовать эмулятор Genymotion, поскольку у него есть красивый виджет эмуляции GPS, который позволяет менять местоположение с помощью интерфейса Google Maps:

Эмуляция локации Genymotion

(Если у вас возникли проблемы с запуском приложения на вашем устройстве, обязательно ознакомьтесь с этим вопросом о переполнении стека при получении Android Studio для распознавания вашего устройства .)

Как только это будет сделано, откройте файл ios / ocdmom .xcworkspace с помощью Xcode. Как только Xcode завершит индексацию файлов, вы сможете нажать эту большую кнопку воспроизведения, и она автоматически запустит приложение на выбранном имитаторе iOS.

Xcode также позволяет вам смоделировать местоположение, но только когда вы создаете приложение для запуска в симуляторе. Внесение изменений в код и обновление сервера разработки фактически не изменит местоположение. Чтобы изменить местоположение, нажмите на значок отправки и выберите местоположение, которое вы хотите использовать:

Симуляция местоположения Xcode

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

Импортируйте пакет Pusher и Background Timer, который вы установили ранее:

1
2
import BackgroundTimer from ‘react-native-background-timer’;
import Pusher from ‘pusher-js/react-native’;

Установите значение для ключа API Google для проекта Google, который вы создали ранее:

1
const GOOGLE_API_KEY = ‘YOUR GOOGLE PROJECT API KEY’;

Используйте API Location и Permissions от Expo:

1
const { Location, Permissions } = Expo;

API Экспо работают кроссплатформенно — это мало чем отличается от стандартного проекта React Native, где вам необходимо установить пакет, такой как React Native Permissions, чтобы получить доступ к API разрешений, который работает кроссплатформенно.

Затем установите интервал (в миллисекундах), который будет выполняться кодом для отслеживания текущего местоположения пользователя. В этом случае мы хотим, чтобы он выполнялся каждые 30 минут. Обратите внимание, что в приведенном ниже коде мы используем значение переменной location_status чтобы проверить, было ли предоставлено разрешение на доступ к текущему местоположению пользователя или нет. Мы установим значение этой переменной позже, когда компонент будет смонтирован:

01
02
03
04
05
06
07
08
09
10
11
12
13
var interval_ms = 1800 * 100;
var location_status = null;
 
BackgroundTimer.runBackgroundTimer(() => { // run the background task
  
  if(location_status == ‘granted’){ // if permission to access the location is granted by the user
 
    // next: add code for getting the user’s current location
   
  }
   
},
interval_ms);

Получить текущее местоположение с помощью API местоположения Expo:

1
2
3
4
5
6
7
8
9
Location.getCurrentPositionAsync({ // get the user’s coordinates
  enableHighAccuracy: true // enable fetching of high accuracy location
})
.then((res) => {
  
  let { latitude, longitude } = res.coords;
 
  // next: add code for getting the address based on the coordinates
});

Затем, используя API геокодирования Карт Google, сделайте запрос к конечной точке обратного геокодирования, указав значения широты и долготы. Это возвращает отформатированный адрес на основе этих координат:

01
02
03
04
05
06
07
08
09
10
11
fetch(`https://maps.googleapis.com/maps/api/geocode/json?latlng=${latitude},${longitude}&key=${GOOGLE_API_KEY}`)
 .then((response) => response.json())
 .then((responseJson) => {
   let addr = responseJson.results[0].formatted_address;
 
   // next: send the location with Pusher
 
 })
 .catch((error) => {
   console.error(error);
 });

Следующим шагом является отправка местоположения с помощью Pusher. Позже мы собираемся создать сервер, который будет служить конечной точкой аутентификации, и в то же время отобразить страницу, которая показывает текущее местоположение пользователя.

Обновите конструктор, чтобы установить значение по умолчанию для экземпляра Pusher:

1
2
3
4
5
6
constructor() {
  /*
  the code for generating unique code from earlier
  */
  this.pusher = null;
}

Когда компонент смонтирован, мы хотим инициализировать Pusher. Теперь вы можете предоставить ключ и кластер Pusher API из настроек приложения Pusher, которое вы создали ранее:

1
2
3
4
5
6
7
componentWillMount() {
  this.pusher = new Pusher(‘YOUR PUSHER APP KEY’, {
    authEndpoint: ‘YOUR AUTH SERVER ENDPOINT (TO BE ADDED LATER)’,
    cluster: ‘YOUR PUSHER CLUSTER’,
    encrypted: true // whether the connection will be encrypted or not.
  });
}

Далее вы можете добавить код для отправки текущего местоположения. В Pusher это делается путем вызова метода trigger() . Первый аргумент — это имя инициируемого события, а второй аргумент — это объект, содержащий данные, которые вы хотите отправить.

Позже на сервере мы подпишемся на тот же канал, на который мы подпишемся после монтирования компонента. Затем мы свяжемся с событием client-location чтобы каждый раз, когда он откуда-то запускался, сервер также получал уведомления (хотя только тогда, когда страница, на которой он обслуживается, также подписана на тот же канал):

01
02
03
04
05
06
07
08
09
10
11
12
13
fetch(…)
 .then(…)
 .then((responseJson) => {
   let addr = responseJson.results[0].formatted_address;
 
   current_location_channel.trigger(‘client-location’, {
     addr: addr,
     lat: latitude,
     lng: longitude
   });
 
 })
 .catch(…);

Единственный раз, когда мы будем запрашивать разрешение на доступ к текущему местоположению пользователя, это когда компонент монтируется. Затем мы обновим location_status на основе выбора пользователя. Значение может быть «предоставлено» или «отказано».

Помните, что код для проверки текущего местоположения пользователя выполняется периодически. Это означает, что новое значение переменной location_status также будет использовано позднее, когда будет выполнена функция. После этого мы также хотим подписаться на канал Pusher, куда будут отправляться обновления местоположения:

01
02
03
04
05
06
07
08
09
10
11
componentDidMount() {
  try {
    Permissions.askAsync(Permissions.LOCATION).then(({ status }) => {
      location_status = status;
    });
  }catch(error){
    console.log(‘err: ‘, error);
  }
  // subscribe to the Pusher channel
  current_location_channel = this.pusher.subscribe(‘private-current-location-‘ + this.state.unique_code);
}

Теперь мы готовы создать сервер. Сначала создайте свой рабочий каталог ( ocdmom -server ) вне каталога проекта приложения. Перейдите в этот каталог и выполните npm init . Просто нажмите Enter, пока он не создаст файл package.json .

Далее установите пакеты, которые нам нужны:

1
npm install —save express body-parser pusher

Вот обзор того, что делает каждый пакет:

  • express : используется для создания сервера. Это отвечает за обслуживание страницы отслеживания, а также за ответ на конечную точку аутентификации.
  • body-parser : промежуточное программное обеспечение Express, которое анализирует тело запроса и делает его доступным как объект JavaScript.
  • pusher : используется для связи с приложением Pusher, которое вы создали ранее.

Как только это будет сделано, ваш файл package.json должен выглядеть следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
{
  «name»: «ocdmom-server»,
  «version»: «1.0.0»,
  «description»: «»,
  «main»: «server.js»,
  «scripts»: {
    «test»: «echo \»Error: no test specified\» && exit 1″,
    «start»: «node server.js»
  },
  «author»: «»,
  «license»: «ISC»,
  «dependencies»: {
    «body-parser»: «^1.18.2»,
    «express»: «^4.16.2»,
    «pusher»: «^1.5.1»
  }
}

Создайте файл server.js и импортируйте только что установленные пакеты:

1
2
3
var express = require(‘express’);
var bodyParser = require(‘body-parser’);
var Pusher = require(‘pusher’);

Сконфигурируйте сервер для использования пакета body-parser и установите общую папку в качестве каталога статических файлов:

1
2
3
4
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static(‘public’));

Инициализируйте Pusher. Указанные здесь значения будут получены из переменных среды. Мы добавим их позже, когда развернем сервер:

1
2
3
4
5
6
var pusher = new Pusher({
  appId: process.env.APP_ID,
  key: process.env.APP_KEY,
  secret: process.env.APP_SECRET,
  cluster: process.env.APP_CLUSTER,
});

Служите странице отслеживания при обращении к базовому URL:

1
2
3
app.get(‘/’, function(req, res){
  res.sendFile(__dirname + ‘/public/tracker.html’);
});

Затем создайте маршрут для ответа на запросы к конечной точке аутентификации. Это будет срабатывать каждый раз, когда приложение инициализирует соединение с Pusher, а также при обращении к странице отслеживания. Это делает аутентификацию пользователей, чтобы они могли общаться с приложением Pusher непосредственно со стороны клиента.

Обратите внимание, что здесь нет никаких мер безопасности. Это означает, что любой может просто сделать запрос к конечной точке авторизации, если у него есть доступ к вашему ключу приложения Pusher. В производственном приложении вам нужна более надежная защита!

1
2
3
4
5
6
7
8
9
app.post(‘/pusher/auth’, function(req, res) {
  var socketId = req.body.socket_id;
  var channel = req.body.channel_name;
  var auth = pusher.authenticate(socketId, channel);
  var app_key = req.body.app_key;
 
  var auth = pusher.authenticate(socketId, channel);
  res.send(auth);
});

Наконец, заставьте сервер прослушивать порт, указанный в переменных среды. По умолчанию это порт 80, но мы также устанавливаем его как альтернативное значение на тот случай, если он не существует:

1
2
var port = process.env.PORT ||
app.listen(port);

На странице отслеживания отображается карта, которая обновляется каждый раз, когда событие client-location запускается из приложения. Не забудьте указать свой ключ API Google:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
  <head>
    <meta name=»viewport» content=»initial-scale=1.0, user-scalable=no»>
    <meta charset=»utf-8″>
    <title>OCDMom Tracker</title>
    <script src=»https://js.pusher.com/4.2/pusher.min.js»></script> <!— the pusher library —>
    <link rel=»stylesheet» href=»css/style.css»>
  </head>
  <body>
    <div id=»map»></div>
     
    <script src=»js/tracker.js»></script> <!— the main JavaScript file for this page —>
 
    <script async defer
    src=»https://maps.googleapis.com/maps/api/js?key=YOUR-GOOGLE-API-KEY&callback=initMap»>
    </script> <!— the google maps library —>
  </body>
</html>

Затем создайте файл public / js / tracker.js и добавьте следующее:

1
2
3
4
5
6
7
8
9
function getParameterByName(name, url) {
    if (!url) url = window.location.href;
    name = name.replace(/[\[\]]/g, «\\$&»);
    var regex = new RegExp(«[?&]» + name + «(=([^&#]*)|&|#|$)»),
        results = regex.exec(url);
    if (!results) return null;
    if (!results[2]) return »;
    return decodeURIComponent(results[2].replace(/\+/g, » «));
}

Функция выше извлекает параметр запроса из URL. Уникальный код (отображаемый в приложении) должен быть включен в качестве параметра запроса при обращении к базовому URL-адресу сервера в браузере. Это позволяет нам отслеживать местоположение пользователя, поскольку он будет подписывать нас на тот же канал, на который подписано приложение.

Затем инициализируйте Pusher. Код похож на код на сервере ранее. Единственное отличие состоит в том, что нам нужно только указать ключ приложения Pusher, конечную точку аутентификации и кластер:

1
2
3
4
5
var pusher = new Pusher(‘YOUR PUSHER APP KEY’, {
  authEndpoint: ‘YOUR PUSHER AUTH ENDPOINT’,
  cluster: ‘YOUR PUSHER CLUSTER’,
  encrypted: true
});

Проверьте, указан ли code в качестве параметра запроса, и подпишитесь на канал Pusher, только если он указан:

1
2
3
4
5
6
7
var channel = null;
 
if(getParameterByName(‘code’) == null){
  alert(‘Make sure that the code is supplied as a query parameter, then refresh the page.’);
}else{
  channel = pusher.subscribe(‘private-current-location-‘ + getParameterByName(‘code’));
}

Добавьте функцию для инициализации карты. Это отобразит карту вместе с маркером, указывающим на местоположение по умолчанию, которое мы указали:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
var map = null;
var marker = null;
 
function initMap(){
  var myLatLng = { // set the default location displayed on the map
    lat: -25.363,
    lng: 131.044
  };
 
  map = new google.maps.Map(document.getElementById(‘map’), {
    zoom: 16,
    center: myLatLng
  });
 
  marker = new google.maps.Marker({
    position: myLatLng,
    map: map
  });
}

Привязать к событию client-location . Функция обратного вызова выполняется каждый раз, когда приложение запускает событие определения client-location которое имеет тот же уникальный код, что и код, предоставленный пользователем в качестве параметра запроса:

1
2
3
4
5
6
7
8
9
if(channel){
  channel.bind(‘client-location’, function(data) {
    console.log(‘message received: ‘, data);
    var position = new google.maps.LatLng(data.lat, data.lng);
    // set it as the position for the marker and the map
    marker.setPosition(position);
    map.setCenter(position);
  });
}

Затем добавьте стили для страницы отслеживания ( public / css / style.css ):

1
2
3
4
5
6
7
8
9
#map {
  height: 100%;
}
 
html, body {
  height: 100%;
  margin: 0;
  padding: 0;
}

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

Установить сейчас глобально:

1
npm install now

После установки вы можете добавить учетные данные приложения Pusher в качестве секретов . Как упоминалось ранее, сейчас бесплатно для проектов с открытым исходным кодом. Это означает, что после развертывания сервера его исходный код будет доступен по пути /_src . Это не очень хорошо, потому что все также могут видеть ваши учетные данные приложения Pusher. Поэтому мы добавим их как секрет, чтобы к ним можно было получить доступ как к переменной среды.

Помните process.env.APP_ID или process.env.APP_KEY из кода сервера ранее? Они устанавливаются как переменные среды через секреты. pusher_app_id — это имя, назначенное для секрета, а YOUR_PUSHER_APP_ID — идентификатор вашего приложения Pusher. Выполните следующие команды, чтобы добавить свои учетные данные приложения Pusher в качестве секретов:

1
2
3
4
now secret add pusher_app_id YOUR_PUSHER_APP_ID
now secret add pusher_app_key YOUR_PUSHER_APP_KEY
now secret add pusher_app_secret YOUR_PUSHER_APP_SECRET
now secret add pusher_app_cluster YOUR_PUSHER_APP_CLUSTER

После того, как вы добавили их, вы можете развернуть сервер. APP_ID — это имя переменной среды, а pusher_app_id — это имя секрета, к pusher_app_id вы хотите получить доступ:

1
now -e APP_ID=@pusher_app_id -e APP_KEY=@pusher_app_key -e APP_SECRET=@pusher_app_secret APP_CLUSTER=@pusher_app_cluster

Вот как это выглядит после развертывания. Возвращаемый URL-адрес является базовым URL-адресом сервера:

развернуть сервер

Скопируйте этот URL в файл App.js и сохраните изменения:

1
2
3
4
5
this.pusher = new Pusher(‘YOUR PUSHER APP KEY’, {
  authEndpoint: ‘https://BASE-URL-OF-YOUR-SERVER/pusher/auth’,
  cluster: ‘YOUR PUSHER APP CLUSTER’,
  encrypted: true
});

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

Это оно! В этой серии из двух частей вы узнали, как отделить существующий проект Expo от ExpoKit. ExpoKit — это хороший способ использовать некоторые инструменты, которые предоставляет платформа Expo, в то время как ваше приложение уже преобразовано в стандартный нативный проект. Это позволяет вам использовать существующие собственные модули для React Native и создавать свои собственные.

Пока вы здесь, ознакомьтесь с некоторыми другими нашими статьями о разработке приложений React Native!

  • Кодирование приложения с помощью GraphQL, React Native и AWS AppSync: серверная часть

  • Начните с React Native Layouts

  • Инструменты для React Native Development

  • Практические примеры анимации в React Native