FaceHost - Статьи

Создаем мобильное приложение на React Native. Часть 2: Системные запросы и API

2024-06-16 18:56 Разработка
В этой части статьи мы перейдем к более интересному этапу разработки. Мы не просто будем менять дизайн программы и добавлять картинки, а запросим информацию о погоде из API и отобразим ее в интерфейсе приложения.
По ходу разработки этих элементов программы вы поймете, что многие моменты, касающиеся логики отображения и изменения контента в приложении, идентичны таковым в React, и писать непосредственно функциональную составляющую здесь гораздо проще, так как нет необходимости в «изобретении велосипедов» и изменении привычного синтаксиса. Мы будем использовать функциональные React-компоненты и хуки.

Добываем местоположение пользователя

В этот раз нам понадобится функциональность, встроенная в операционную систему Apple (как и в Android, на самом деле). В iOS есть специальный модуль API, отвечающий за работу с GPS. То есть для запроса информации о местоположении пользователя. Вы наверняка и сами не раз сталкивались с тем, что встроенные и сторонние приложения запрашивают доступ к геопозиции через специальное системное окно. Это окно нам как раз и понадобится.
Так как мы делаем приложение, показывающее погоду, то без информации о расположении устройства не обойтись. Это базовая функциональность, и нам необходимо ее учесть в своем приложении тоже.
Для доступа к подобным функциям операционной системы необходимо использовать соответствующие модули из набора API самой Apple. Так как при работе с React Native мы не можем получить доступ к API калифорнийской корпорации напрямую, нам нужно воспользоваться уже адаптированным под эту деятельность RN-модулем.
Раньше такой модуль был у Facebook, но он устарел и больше не поддерживается, так что придется обратиться за помощью к сторонним решениям от энтузиастов. Я использовал React-Native-Geolocation-Service, потому что ее создатель пытался сделать максимально простое решение, способное стать полноценной аналогом официальной геолокационной библиотеки.

Устанавливаем react-native-geolocation-service

Чтобы установить сторонний RN-модуль в свое приложение:
  • Открываем командую строку в корневой директории нашего проекта и вводим команду npm install react-native-geolocation-service
  • После этого закрываем наш проект и переходим в папку ios, чтобы установить pod-компонент, необходимый для взаимодействия с системными API для получения информации с GPS-модуля устройства: cd ios
  • Затем вводим команду для установки пода. Если этого не сделать, программа будет жаловаться на отсутствие нужных утилит: pod install.
  • Затем вновь возвращаемся в корневую директорию: cd ..
  • Снова запускаем наш проект при помощи команды npx react-native run-ios
Сразу предупреждаю, что вас вновь ждет довольно длительный процесс сборки и настройки, так как добавился новый компонент, и он должен стать частью нового ПО. Так будет происходить каждый раз при добавлении новых программных модулей в разрабатываемое приложение.
После загрузки приложения переходим к запросу местоположения устройства.

Настраиваем запрос геопозиции

Перед тем как перейти непосредственно к взаимодействию с геолокационным модулем, загруженным из стороннего репозитория, необходимо импортировать его в наше приложение.
Это делается простой командой import, ранее мы ее уже использовали:
import Geolocation from 'react-native-geolocation-service
Теперь мы можем напрямую обратиться к классу Geolocation и его методам, чтобы тот уже провзаимодействовал с системой на уровне нативного кода и достал для нас информацию, снятую с GPS-модуля устройства.

Запрашиваем геопозицию

Запрос к местоположению устройства осуществляется при помощи функции, отправляющей запрос к аппаратной составляющей гаджета. В первую очередь нас интересует разрешение пользователя на отслеживание его геопозиции, а затем уже непосредственно добыча данных.
Перед тем как запросить местоположение устройства, мы создадим две переменные, где будет храниться информация, добытая с GPS-сенсора.
Нас интересуют такие параметры, как долгота и широта (они будут использоваться для запроса погоды в конкретном местоположении). В английском эти единицы называются latitude и longtitude. Поэтому создадим два соответствующих состояния.
const [lat, setLat] = useState(0)
const [lon, setLon] = useState(0)
Для работы с useState необходимо импортировать useState следующим образом: import { useState } from 'react'
Здесь мы сможем хранить широту и долготу, чтобы другие элементы приложения могли в любой момент получить эту информацию.
Теперь пишем функцию запроса к геопозиции:
Мы используем хук useEffect, чтобы геопозиция пользователя запрашивалась при запуске программы (еще и при любом изменении состояния, но это мы исправим позже). Внутри useEffect создаем анонимную функцию:
() => {}
В теле функции делаем асинхронный запрос к GPS-модулю с использованием аргумента ‘whenInUse’, так как мы хотим получить от пользователя разрешение на отслеживание местоположения, пока программа открыта. И если пользователь дал добро, и программа на запрос выдает статус ‘granted’, то можно смело просить текущую геопозицию пользователя и обрабатывать полученные данные.
let response = Geolocation.requestAuthorization('whenInUse').then(x => { if (x == 'granted') { } }
В теле конструкции if мы непосредственно получаем доступ к объекту position, который покажет нам ширину с долготой и позволит эту информацию сохранить для последующего использования.
Geolocation.getCurrentPosition(position => { })
При получении объекта position, разбираем его на части и сохраняем в состояние (мы используем toFixed, чтобы хранить приблизительное значение без точности до метра).
setLat(position.coords.latitude.toFixed(0))
Запрашиваем долготу:
setLon(position.coords.longtitude.toFixed(0))
Готово. Теперь в состоянии программы хранится необходимая нам информация. А это значит, что теперь можно приступать к функции, которая будет запрашивать погоду через API и сохранять ее в нужном для нас состоянии (или в переменной).
В симуляторе геолокация равна тому значению, что укажет пользователь в соответствующем пункте меню. Нужно открыть раздел Features, потом перейти в пункт Location и выбрать Custom Location. После этого устройство будет думать, что находится в указанной зоне.

Запрашиваем погоду по геолокации

Начнем с доступа к API-ключу, а закончим отрисовкой погоды в интерфейсе программы.

Регистрируемся на openWeatherMap

Чтобы использовать API, зачастую нужен специальный ключ, позволяющий конкретно вашему приложению получать необходимую информацию. Это делается, в частности, для борьбы с избыточным использованием API, чтобы те, кто не платит, не занимали слишком много ресурсов (эта проблема решается платной подпиской на API).
Чтобы получить ключ:
  • Переходим на сайт openWeatherMap.
  • Регистрируемся.
  • Заходим в свой профиль и находим там пункт API. Копируем значение этого пункта.
Готово. У нас есть ключ, и мы можем использовать его в своем приложении на бесплатной основе (при использовании бесплатных функций сервиса openWeatherMap).

Создаем точку доступа для общения с API

Сейчас мы напишем функцию, которая запрашивает данные о погоде с openWeatherMap и передает нам объект с соответствующими данными. Но эти данные необходимо где-то хранить, поэтому мы сделаем отдельное состояние с текущей температурой. Назовем его weatherData.
const [weatherData, setWeatherData] = useState(0)
Теперь создадим функцию getWeather, обращающуюся к API и фиксирующую информацию о текущей температуре и других погодных параметрах.
  1. Сначала мы объявляем асинхронную функцию getWeather:
const getWeather = async ( ) => { }
  1. В теле функции необходимо создать переменную, в которой будет содержаться запрос к API и ответ от сервера openWeatherMap. Здесь будет использоваться спецссылка, в ней необходимо указать широту и долготу точки на Земле, для которой мы хотим узнать погоду. Также в ней указывается наш API-ключ. Широту и долготу возьмем из состояния программы.
const weather = await fetch([указываем ссылку на API в обратных кавычках, в полях lat и lon оставляем ссылки на состояние приложения]&appid=[API-ключ])
  1. Преобразуем ответ от API в формат JSON:
const data = await weather.json()
  1. Записываем полученную температуру в состояние weatherData:
setWeatherData(data.main.temp)
Готово. Мы получили нужную информацию, и теперь она срабатывает каждый раз, когда происходит обновление состояния программы.

Настраиваем отображение погоды в интерфейсе

Наши цифры просто хранятся в состоянии, и пользователь приложения их увидеть не сможет. Нам нужно создать элемент интерфейса, показывающий текущую температуру. Для этого необходимо добавить в верстку компонент View с соответствующим содержимым.
  1. Внутри компонента View необходимо создать еще один компонент для отображения текста. Сразу стилизуем его, изменив размер текста и его шрифт (пока не особо симпатично, но уже что-то):
<Text style={{color: 'red', marginHorizontal: 'auto', fontSize: 40}}> </Text>
  1. Внутри будет текст на ваше усмотрение и информация о погоде, хранящаяся в состоянии weatherData.
[какой-то текст] {weatherData.toFixed(0)}
toFixed(0) используется, чтобы показывать только целые значения и скрыть числа за точкой/запятой.

Создаем отдельную функцию для запроса геопозиции и отображения погоды

В нашем текущем приложении есть проблема, связанная с постоянными запросами к GPS-модулю и обновлением текущих погодных условий. Это не особо эффективно с точки зрения использования ресурсов программы, поэтому лучше выделить логику, которая касается именно отображения погодных условий на основе геопозиции устройства, в отдельную функцию и запустить ее вручную.
Реализовать это легко. Мы просто вытащим уже существующую функцию из useEffect и поместим ее в отдельную переменную с другим названием, например getLocationWeather.
Теперь мы можем запускать эту функцию вручную. Например, по нажатию на кнопку.
Мы создадим кликабельный элемент интерфейса в виде значка GPS и сделаем так, чтобы при нажатии на него срабатывала функция getLocationWeather.
Вместо стандартного элемента Button воспользуемся компонентом TouchableOpacity, так как Button менее абстрактный и не позволяет себя стилизовать, а вот TouchableOpacity может принять любой облик на ваш вкус. И он может спокойно стать картинкой, если это понадобится.
Создаем компонент TouchableOpacity, предварительно импортировав его в приложение. И одновременно с этим проводим базовую стилизацию, а также назначаем действие на нажатие по этой кнопке:
<TouchableOpacity style={{borderWithin: 2, borderRadius: 10, padding: 5}} onPress={getLocationWeather}> </TouchableOpacity>
Внутри можно указать любой другой компонент. В нашем случае это будет заранее загруженная картинка. Я оставил ее в корневой директории приложения.
<Image style={{width: 20, height: 20}} source={require('./gps.png'} />
Готово. Теперь мы вручную запрашиваем геопозицию и погоду на ее основе. К сожалению, такой подход создает другую проблему. Из-за того, что useState – это асинхронная функция, мы не увидим результата ее работы сразу после нажатия на значок GPS. Это произойдет только после повторного нажатия. Будет складываться ощущение, будто приложение отстает на шаг от действий пользователя. Мы решим эту проблему позднее, когда вновь будем редактировать функцию useEffect ниже.

Запрашиваем погоду по выбранному городу

Есть вероятность, что человек не захочет делиться с приложением геопозицией или просто захочет посмотреть погоду в каком-то другом городе, а не в своем. Для этого необходимо реализовать поиск широты и долготы других локаций при помощи какого-нибудь текстового поля и клавиши для ручного запроса погоды в конкретной локации. Это можно реализовать, используя API все той же openWeatherMap.

Настраиваем поле для поиска города

Сначала создадим интерфейс, с которым мы будем взаимодействовать в поисках города. Он будет состоять из примитивного текстового поля, состояния с названием города и кнопки для вызова функции поиска информации о погодных условиях в выбранном городе.
Добавляем базовый компонент Input, предварительно импортировав его в программу: <Input />
Внутри компонента нужно прописать такие атрибуты, как:
  • placeholder – чтобы пользователь видел, что в это поле можно ввести название города.
  • value – чтобы отображать в текстовом поле значение из состояния, в котором мы будем хранить название города (это так называемый контролируемый инпут-компонент): value={city}
  • onChangeText – чтобы передавать значение инпут-компонента в состояние, где хранится название города: onChangeText={setCity}
Теперь нужно организовать состояние, хранящее название города:
const [city, setCity] = useState('')
Остается добавить кнопку, активирующую API и добывающую для нас необходимую информацию о погоде.
  1. Импортируем в программу компонент Button:
<Button>
  1. Добавляем ему свойство title, чтобы заменить текст на get weather.
title="get weather"
  1. И подключаем отслеживание нажатий с применением функции getWeatherInTheCity (мы создадим ее в следующей главе).
onPress={getWeatherInTheCity}
Мы используем тут обычную кнопку вместо TouchableOpacity, так как нет необходимости в какой-то глубокой стилизации или замене текста на картинку. Здесь нам подходит самая примитивная системная кнопка без изысков.

Настраиваем точку доступа к API

Нам нужна еще одна асинхронная функция, показывающая информацию о городе, указанном в текстовом поле. Она не особо отличается от той же функции, что отвечает за поиск погодных данных, нужно лишь заменить API-ссылку.
В разделе Geocoding API на сайте openWeatherMap можно найти документацию конкретно по этой функции. Принцип работы у нее простой – указываем название города и получаем его широту с долготой, а их уж использовать мы умеем.
  1. Объявим функцию с названием getWeatherInTheCity.
const getWeatherInTheCity = async () => { }
  1. В теле функции создадим переменную, хранящую в себе запрос к API и ответ от сервера openWeatherMap.
const geo = await fetch('https://api.openweathermap.org.geo/1.0/direct?q=${city}&limit=5&appid=[API-ключ]'
  1. Следом объявляем переменную, хранящую ответ сервера, преобразованный в формат JSON.
const data = await geo.json()
  1. Полученную информацию сохраняем в состояния lat и lon, как мы уже делали ранее при запросе геопозиции устройства. Например, так:
setLat(data[0].lat.toFixed(0))
  1. И здесь же вновь вызываем функцию getWeather, чтобы обновить информацию о погоде в интерфейсе:
getWeather()
Готово! У нас есть механизм, использующий значение в переменной city для поиска города в базе openWeatherMap и последующего запроса погоды в выбранной локации.
Получившаяся программа еще далека от типичных представителей этого рынка, но внушительную часть функциональности нам добавить все же удалось.

Решаем оставшиеся проблемы с useEffect

Напоминаю, что у нас еще осталась проблема – независимо от того, каким образом мы запрашиваем текущую температуру, мы все равно отстаем на шаг.
Если зайти в программу и нажать на значок GPS, мы получим температуру, не соответствующую реальной, даже если указать другую геопозицию в настройках. Почему? Из-за базовых значений lat и lon. Оба значения равны нулю по умолчанию, и мы находим погоду для локации, расположенной в 0 и 0.
Но если еще раз нажать на тот же значок, то информация обновится и вывод покажет уже предыдущие (обновленные) значения lat и lon. Так будет происходить всегда, потому что useState – асинхронная функция, не успевающая передать данные к моменту перерисовки интерфейса.
Это можно исправить при помощи хука useEffect. Он может запускать какую-либо функцию при перерисовке интерфейса. Можно добавить туда функцию обновления ширины с долготой и параллельно функцию обновления данных о погоде. Но в таком случае мы всегда будет получать только погоду в текущем местоположении пользователя. Изменить это можно, если добавить в useEffect условие.
Например, если поле lat уже заполнено и не равняется 0, то можно просто запросить погоду при перерисовке интерфейса. А если же поле lat равняется 0, то можно сначала запросить местоположение пользователя, а потом уже и погоду.
Таким образом мы будем получать актуальные погодные условия при первом запуске приложения и правильные данные при ручном вводе города.
Чтобы сделать этот метод еще эффективнее, можно использовать хук useAppState, созданный сообществом React Native. С помощью него можно четко отследить факт открытия программы, а не перерисовки интерфейса. Получится использовать функции, связанные с API, более эффективно и не наживать себе проблемы, когда реально понадобится найти данные по нулевой долготе/ширине.

Вместо заключения

Мы научили нашу мобильную программу спрашивать разрешение на отслеживание геопозиции и показывать текущую температуру за окном. Это пока далеко не Carrot Weather и даже не стандартная погодная утилита для iOS, но уже рабочий проект, который можно довести до ума. Теперь вы знаете базовые аспекты работы с React Native.
Далее мы сделаем поиск по городам с подсказками и прогноз погоды на ближайшие несколько дней.