짜잔 사실 오늘자로 플젝 끝났지롱
까먹기 전에 부지런히 기록 🏃🏻🏃🏻♀️ 우리 팀 너무 고생 많았당!😊
react-native-geolocation-service 라이브러리를 사용했다.
https://github.com/Agontuk/react-native-geolocation-service
1. 사용자가 뛴 거리 계산
2. 사용자의 이동 경로 지도에 표시하기
3. 러닝 일시중지 시 위치 업데이트 중지
크게 세 가지의 API가 있다.
watchPosition
메소드가 리턴하는 watchId
를 인자로 받아 해당 watchPosition을 제거한다.라이브러리 설명만 봐서는 어떻게 사용하는건지 감이 잘 오지 않았다.
사용해본 입장에서 설명을 조금 더 덧붙이자면
getCurrentPosition
메소드의 경우 일반 함수와 다를 게 없다.
일반함수를 호출하듯 getCurrentPosition
을 호출하면, 호출 시점의 위치를 바로 받아오게 된다.
watchPosition
이 조금 어려웠는데, 이 메소드를 호출하는 건 마치 addEventListener
를 호출하는 것과 같다. 이벤트를 감지하는 이벤트리스너를 부착하듯이 사용자의 위치 변화를 감지하는 트래커를 부착하는 것이다. clearWatch
를 하기 전까진 계속해서 사용자의 업데이트된 위치를 리턴한다.
첫번째 목표부터 차근차근 구현해보자.
첫번째 목표는 사용자가 뛴 거리를 계산하는 것이었다!
그러기 위해서는 사용자의 위치가 변할 때마다 사용자의 위치를 받아와야 했다.
다만 아무때나 받아오는 게 아니라 사용자가 러닝을 뛰고 있는 상태일 때! 즉 RunningScreen이 화면에 렌더링되어 있을 때만 거리가 업데이트 되어야 한다.
따라서 저번 글에서 활용했던 navigation.addListener
와 Geolocation을 함께 사용했다.
RunningScreen에 화면 포커스가 감지되면 watchPosition을 부착하고, 화면 blur가 감지되면 clearWatch로 위치 트래킹을 멈췄다.
이렇게 하면 위에서 기술한 구현목표 1과 3을 달성할 수 있당.👏🏻
useRef
로 watchId값을 관리해줬다. watchId값의 업데이트는 리렌더링을 필요로 하지 않으니까!
거리 계산을 위해 직전에 받아온 위경도를 저장해뒀어야 했는데, 이 값 또한 useRef로 관리했다. 화면에 표시되지 않는 정보이므로.
대신 뛴 거리 업데이트는 화면에 바로 바로 반영이 되어야 했으므로 useState를 사용했다.
watchPosition의 success Callback이 새로운 위치(위도, 경도)를 리턴할 때마다 calDistance
함수로 거리를 계산해 totalDist
state를 업데이트했다.
calDistacne는 아래 링크에 있는 코드를 참고했다. 근데 정확도는 잘 모르겠다. 🤔
NCR이랑 비교하면서 뛰어본 적이 있었는데 NCR에 비해 거리 증가 속도가 확연히 빨랐다.
https://github.com/ReactNatives-Youtube/Nike-Run-Club/blob/master/constants/CalculationsPage.js
내가 직접 핸드폰 두 개 들고 걸어다니면서 실험한 결과다^_^
우리 앱(BurnIN)의 거리가 업데이트 될 때마다 NikeRunClub을 확인해 기록한건데, 우리 어플의 증가 속도가 훨씬 빨랐다. 그래도 보면 우리 어플이 거리를 업데이트 할 때마다 NikeRunClub의 거리는 0.1Km씩 증가한 걸 볼 수 있음. 만약 내가 이 라이브러리를 다시 이용한다면 따로 거리 계산식을 쓰지 않고 watchPosition의 successCallback이 호출될 때마다 0.1Km씩 증가시켜도 좋을 거 같다.
아래는 위 내용에 해당하는 코드
function RunningScreen(){
const watchId = useRef(null);
const [totalDist, setTotalDist] = useState(0);
const removeLocationUpdates = () => {
if (watchId.current != null) {
Geolocation.clearWatch(watchId.current);
}
};
const getLocationUpdates = () => {
watchId.current = Geolocation.watchPosition(
position => {
const {latitude, longitude} = position.coords;
const newLocation = {latitude: latitude, longitude: longitude};;
const newDist = calDistance(
lat.current,
lon.current,
latitude,
longitude,
);
setTotalDist(prevDist => parseFloat(prevDist) + parseFloat(newDist));
lat.current = latitude;
lon.current = longitude;
},
error => {
console.log(error);
},
{
accuracy: {
android: 'high',
},
enableHighAccuracy: true,
},
);
};
useEffect(() => {
navigation.addListener('focus', e => {
getLocationUpdates();
});
navigation.addListener('blur', e => {
removeLocationUpdates();
});
}, [navigation]);
}
이건 구글맵 라이브러리를 이용했다.
react-native-maps 이용!
react-native-maps의 Mapview 컴포넌트의 자식 컴포넌트로 Polyline이라는 컴포넌트를 넣어주면 된다. Polyline의 인자로 위경도가 담겨있는 객체들의 배열을 넘겨주면 된다.
위 코드에서 successCallback이 호출될 때마다 dispatch({type: 'UPDATE_LOCATION', payload: newLocation});
dispatch로 locations
배열에 새로운 객체를 concat 해주었다.
<MapView
initialRegion={{
latitude: 37.56578,
longitude: 126.9386,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
style={styles.map}>
<Polyline coordinates={locations} strokeColor="#EF9917" />
</MapView>
요번 프로젝트에서는 처음으로 Redux가 아닌 ContextAPI를 사용했었는데 이 내용도 곧 정리해봐야겠다. 오늘 정리 끝! 😎
혹시 react-native-geolocation-service 백그라운드 상태일때(앱사용도중 전화, 카톡,홈화면 내렷을떄) 데이터받는 로직도 구현하셧나요 ?