Функция очистки геолокации (watchId) не останавливает отслеживание местоположения (React Native)

#react-native #geolocation #react-hooks #react-functional-component #use-ref

#react-native #геолокация #реагирующие крючки #реагирующий функциональный компонент #использовать-ссылка

Вопрос:

Я пытаюсь создать простой пример отслеживания местоположения, и я застрял в следующем случае. Моя основная цель — переключить просмотр местоположения, нажав кнопку start / end. Я разделяю проблемы, внедряя пользовательский реагирующий хук, который затем используется в компоненте приложения:

useWatchLocation.js

 import {useEffect, useRef, useState} from "react"
import {PermissionsAndroid} from "react-native"
import Geolocation from "react-native-geolocation-service"

const watchCurrentLocation = async (successCallback, errorCallback) => {
  if (!(await PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION))) {
    errorCallback("Permissions for location are not granted!")
  }
  return Geolocation.watchPosition(successCallback, errorCallback, {
    timeout: 3000,
    maximumAge: 500,
    enableHighAccuracy: true,
    distanceFilter: 0,
    useSignificantChanges: false,
  })
}

const stopWatchingLocation = (watchId) => {
  Geolocation.clearWatch(watchId)
  // Geolocation.stopObserving()
}

export default useWatchLocation = () => {
  const [location, setLocation] = useState()
  const [lastError, setLastError] = useState()
  const [locationToggle, setLocationToggle] = useState(false)
  const watchId = useRef(null)

  const startLocationWatch = () => {
    watchId.current = watchCurrentLocation(
      (position) => {
        setLocation(position)
      },
      (error) => {
        setLastError(error)
      }
    )
  }

  const cancelLocationWatch = () => {
    stopWatchingLocation(watchId.current)
    setLocation(null)
    setLastError(null)
  }

  const setLocationWatch = (flag) => {
    setLocationToggle(flag)
  }

  // execution after render when locationToggle is changed
  useEffect(() => {
    if (locationToggle) {
      startLocationWatch()
    } else cancelLocationWatch()
    return cancelLocationWatch()
  }, [locationToggle])

  // mount / unmount
  useEffect(() => {
    cancelLocationWatch()
  }, [])

  return { location, lastError, setLocationWatch }
}
  

App.js

 import React from "react"
import {Button, Text, View} from "react-native"

import useWatchLocation from "./hooks/useWatchLocation"

export default App = () => {
  const { location, lastError, setLocationWatch } = useWatchLocation()
  return (
    <View style={{ margin: 20 }}>
      <View style={{ margin: 20, alignItems: "center" }}>
        <Text>{location amp;amp; `Time: ${new Date(location.timestamp).toLocaleTimeString()}`}</Text>
        <Text>{location amp;amp; `Latitude: ${location.coords.latitude}`}</Text>
        <Text>{location amp;amp; `Longitude: ${location.coords.longitude}`}</Text>
        <Text>{lastError amp;amp; `Error: ${lastError}`}</Text>
      </View>
      <View style={{ marginTop: 20, width: "100%", flexDirection: "row", justifyContent: "space-evenly" }}>
        <Button onPress={() => {setLocationWatch(true)}} title="START" />
        <Button onPress={() => {setLocationWatch(false)}} title="STOP" />
      </View>
    </View>
  )
}
  

Я искал несколько примеров, которые есть в Сети, и приведенный выше код должен работать. Но проблема в том, что при нажатии кнопки остановки местоположение по-прежнему обновляется, даже если я вызываю Geolocation.clearWatch (watchId).

Я завернул вызовы геолокации для обработки разрешения местоположения и других возможных отладочных материалов. Похоже, что значение watchId, сохраненное с помощью useRef хук внутри useWatchLocation, недопустимо. Мое предположение основано на попытке вызвать Geolocation.stopObserving() сразу после Geolocation.clearWatch(watchId). Подписка прекращается, но я получаю предупреждение:

Вызывается stopObserving с существующими подписками.

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

Что я упускаю / делаю неправильно?

РЕДАКТИРОВАТЬ: я нашел решение. Но поскольку смонтированный шаблон обычно считается антишаблоном: у кого-нибудь есть лучшее решение?

Ответ №1:

Хорошо, проблема решена с помощью смонтированного шаблона. Смонтировано.значение current установлено в locationToggle значение effect true , а внутри cancelLocationWatch — в false :

 const isMounted = useRef(null)

...
    
useEffect(() => {
        if (locationToggle) {
          isMounted.current = true              // <--
          startLocationWatch()
        } else cancelLocationWatch()
        return () => cancelLocationWatch()
      }, [locationToggle])
    
...    

const cancelLocationWatch = () => {
        stopWatchingLocation(watchId.current)
        setLocation(null)
        setLastError(null)
        isMounted.current = false               // <--
      }
  

И проверяется при эффекте монтирования / размонтирования, успешном и ошибочном обратном вызове:

 const startLocationWatch = () => {
    watchId.current = watchCurrentLocation(
      (position) => {
        if (isMounted.current) {                // <--
          setLocation(position)
        }
      },
      (error) => {
        if (isMounted.current) {                // <--
          setLastError(error)
        }
      }
    )
  }
  

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

1. Я думаю, что это оправданный вариант использования isMounted шаблона