地理位置clearWatchwatchId不会停止位置跟踪React Native

问题描述

我正在尝试创建位置跟踪器的简单示例,并且遇到以下情况。我的基本目标是通过按开始/结束按钮来切换位置监视。我正在通过实现自定义react钩子来分离关注点,然后将其用于App组件中:

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,setLocationWatch } = useWatchLocation()
  return (
    <View style={{ margin: 20 }}>
      <View style={{ margin: 20,alignItems: "center" }}>
        <Text>{location && `Time: ${new Date(location.timestamp).toLocaleTimeString()}`}</Text>
        <Text>{location && `Latitude: ${location.coords.latitude}`}</Text>
        <Text>{location && `Longitude: ${location.coords.longitude}`}</Text>
        <Text>{lastError && `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)

,当按下停止按钮时,位置仍然保持更新。

我包装了Geolocation调用以处理位置许可和其他可能的调试内容。使用 useWatchLocation 中的 useRef 钩子保存的 watchId 值似乎无效。我的猜测是基于尝试在Geolocation.clearWatch(watchId)之后立即调用 Geolocation.stopObserving()的。订阅停止,但我得到警告:

称为stopObserving与现有订阅

所以我认为原始订阅未被清除。

我错过了什么/做错了什么?

编辑:我想出了解决方案。但是由于isMounted模式通常被视为反模式:有人能找到更好的解决方案吗?

解决方法

好, isMounted模式解决了问题。 isMounted.current在locationToggle效果下设置为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)
        }
      }
    )
  }