이 글에서 정리하는 내용
이 글에서는 Expo Location이 무엇인지부터 시작해 현재 위치를 한 번 가져오는 방법, 위치 변화를 실시간으로 구독하는 방법, 그리고 백그라운드 위치 추적과 지오펜싱이 왜 더 복잡한지까지 순서대로 정리합니다. 단순히 API 이름만 나열하지 않고, 어떤 상황에서 어떤 메서드를 써야 하는지, 왜 권한 요청과 앱 설정이 먼저 필요한지, Expo Go와 development build의 차이까지 함께 설명합니다. 끝까지 읽으면 해비님이 위치 기능을 붙일 때 어떤 흐름으로 구현해야 하는지 판단할 수 있게 됩니다.
- Expo Location은 무엇을 하는 라이브러리인가
- 위치 권한과 app config를 먼저 이해해야 하는 이유
- 현재 위치를 한 번 가져오는 가장 기본적인 방법
- 실시간 위치 추적과 watchPositionAsync의 역할
- 백그라운드 위치와 지오펜싱이 더 어려운 이유
- 정리
- FAQ
Expo Location은 무엇을 하는 라이브러리인가
![[React Native] Expo Location 사용법 – 현재 위치 조회, 실시간 추적, 백그라운드 위치까지 정리 1 ChatGPT Image 2026년 3월 10일 오후 06 06 49](https://blogflow.kr/wp-content/uploads/2026/03/ChatGPT-Image-2026년-3월-10일-오후-06_06_49-1024x683.png)
Expo Location은 기기의 위치 정보를 읽기 위한 라이브러리입니다. 가장 단순한 사용은 현재 위치를 한 번 가져오는 것이지만, 실제로는 그보다 범위가 더 넓습니다. 현재 좌표 조회, 위치 변화 구독, 주소를 좌표로 바꾸는 지오코딩, 좌표를 주소로 바꾸는 리버스 지오코딩, 백그라운드 위치 업데이트, 지오펜싱까지 이어집니다.
처음에는 모두 비슷해 보여도 해결하는 문제가 다릅니다. 날씨 앱처럼 지금 위치를 한 번만 알면 되는 경우도 있고, 러닝 앱처럼 계속 이동 경로를 추적해야 하는 경우도 있습니다. 또 특정 반경 안에 들어왔는지를 감지해야 하는 서비스라면 지오펜싱이 필요합니다. 이 글은 초심자가 가장 이해하기 쉬운 순서로 현재 위치 조회, 실시간 추적, 백그라운드 위치와 지오펜싱 순서로 설명합니다. 즉, Expo Location은 위치 기능을 단계적으로 확장할 때 기준이 되는 라이브러리라고 볼 수 있습니다.
기능을 먼저 크게 나누어 보기
import * as Location from 'expo-location';
const oneTime = Location.getCurrentPositionAsync;
const watch = Location.watchPositionAsync;
const geocode = Location.geocodeAsync;
const reverse = Location.reverseGeocodeAsync;
const background = Location.startLocationUpdatesAsync;
const geofencing = Location.startGeofencingAsync;
위 코드는 실제 동작용 예제라기보다 Expo Location의 기능 범위를 한눈에 보기 위한 정리입니다. 이름만 봐도 현재 위치 1회 조회, 실시간 구독, 주소 변환, 백그라운드 추적, 지오펜싱으로 역할이 나뉘는 것을 확인할 수 있습니다.
초심자라면 먼저 현재 위치 조회와 실시간 추적만 확실히 구분해도 충분합니다. 백그라운드 위치와 지오펜싱은 그다음 단계입니다. 이 둘은 권한, 네이티브 설정, 테스트 환경까지 함께 이해해야 하므로 난도가 한 단계 올라갑니다.
위치 권한과 app config를 먼저 이해해야 하는 이유
위치 기능은 코드보다 권한 흐름에서 더 자주 막힙니다. 버튼을 눌렀는데 위치가 안 나오는 경우, API가 틀린 것이 아니라 권한을 요청하지 않았거나 기기 설정에서 위치 서비스가 꺼져 있는 경우가 많습니다. 그래서 Expo Location은 설치 후 바로 메서드를 호출하기보다, 먼저 권한과 앱 설정을 이해하는 것이 중요합니다.
이 구간은 어렵게 느껴질 수 있지만, 사실은 두 단계로만 나누어 생각하면 됩니다. 현재 위치만 앱이 열려 있을 때 쓸 것인지, 아니면 앱이 뒤로 가도 계속 추적할 것인지입니다. 이 두 상황을 분리해서 이해하면 권한 구조도 훨씬 단순해집니다.
현재 위치만 필요할 때
앱이 켜져 있는 동안 현재 위치를 읽기만 하면 되는 경우에는 포그라운드 권한이 핵심입니다. 예를 들어 날씨 앱, 현재 위치 기반 매장 목록, 배송지 자동 입력 같은 기능은 대부분 이 범위 안에서 해결됩니다. 이 단계에서는 requestForegroundPermissionsAsync와 현재 위치 조회 메서드를 먼저 이해하면 됩니다.
백그라운드 추적까지 필요할 때
앱이 화면 뒤로 가도 위치 이벤트를 받아야 한다면 이야기가 달라집니다. 이 경우에는 백그라운드 권한, TaskManager 등록, 플랫폼별 설정, development build 여부까지 확인해야 합니다. 즉 백그라운드 위치는 단순히 메서드 하나를 더 배우는 것이 아니라, 앱 설정과 운영 방식까지 같이 다뤄야 하는 주제입니다.
기본 설치와 config plugin 예제
{
"expo": {
"plugins": [
[
"expo-location",
{
"locationWhenInUsePermission": "앱 사용 중 현재 위치를 사용합니다.",
"locationAlwaysAndWhenInUsePermission": "백그라운드에서도 위치가 필요합니다.",
"isIosBackgroundLocationEnabled": true,
"isAndroidBackgroundLocationEnabled": true,
"isAndroidForegroundServiceEnabled": true
}
]
]
}
}
이 설정은 모든 프로젝트에 무조건 넣는 것이 아니라, 실제로 필요한 범위까지만 넣는 것이 좋습니다. 현재 위치만 한 번 가져오는 앱이라면 백그라운드 관련 옵션까지 미리 켜둘 필요는 없습니다. 기능보다 권한이 더 넓으면 사용자가 앱을 불필요하게 무겁게 느낄 수 있습니다.
또 하나 중요한 점은 백그라운드 위치를 다룰 때 Expo Go로는 한계가 있다는 것입니다. background location은 development build가 필요합니다. 초심자가 이 부분을 모르고 Expo Go에서만 테스트하다가 코드가 틀렸다고 오해하는 경우가 많습니다.
| 기능 | 필요한 것 | 테스트 환경 |
|---|---|---|
| 현재 위치 1회 조회 | 포그라운드 권한 | Expo Go 가능 |
| 백그라운드 위치 추적 | 백그라운드 권한, TaskManager, 앱 설정 | development build 권장 |
현재 위치를 한 번 가져오는 가장 기본적인 방법
가장 먼저 익혀야 할 메서드는 requestForegroundPermissionsAsync와 getCurrentPositionAsync입니다. 흐름은 단순합니다. 먼저 위치 권한을 요청하고, 허용되면 현재 위치를 가져옵니다. 이때 핵심은 위치 조회보다 권한 분기 처리입니다. 권한이 거부된 상태에서 바로 위치를 가져오려고 하면 정상적으로 동작할 수 없습니다.
현재 위치 1회 조회 기본 예제
import { useEffect, useState } from 'react';
import { Linking, Text, View } from 'react-native';
import * as Location from 'expo-location';
export default function App() {
const [text, setText] = useState('위치를 확인하는 중입니다.');
useEffect(() => {
const getCurrentLocation = async () => {
const permission = await Location.requestForegroundPermissionsAsync();
if (!permission.granted) {
if (!permission.canAskAgain) {
setText('권한이 영구적으로 거부되어 설정에서 허용해야 합니다.');
Linking.openSettings();
return;
}
setText('위치 권한이 거부되었습니다.');
return;
}
const currentLocation = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.Balanced,
});
setText(JSON.stringify(currentLocation.coords));
};
getCurrentLocation();
}, []);
return (
<View>
<Text>{text}</Text>
</View>
);
}
이 예제에서 가장 먼저 봐야 하는 것은 requestForegroundPermissionsAsync입니다. 많은 분이 위치를 가져오는 함수만 보지만, 실제 시작점은 권한 요청입니다. 사용자가 허용하지 않으면 이후 흐름은 진행되지 않습니다. 그래서 권한 상태에 따라 문구를 바꾸거나, 설정 화면으로 유도하는 처리까지 나중에는 같이 고려해야 합니다.
특히 canAskAgain이 false인 경우는 단순 거부보다 더 중요합니다. 이 상태는 앱이 다시 권한 팝업을 띄워도 해결되지 않을 수 있어, 사용자가 직접 설정에서 권한을 허용해야 하는 상황일 수 있습니다. 실무에서는 이 분기를 넣어두지 않으면 사용자가 왜 계속 안 되는지 이해하지 못하는 문제가 자주 생깁니다.
정확도는 처음부터 최고로 올릴 필요가 없습니다. 예를 들어 단순 날씨 조회나 동네 추천처럼 대략적인 위치만 있어도 되는 기능이라면 Balanced 정도로 시작해도 충분한 경우가 많습니다. 정확도를 높일수록 응답 속도와 배터리 사용량에 영향을 줄 수 있기 때문입니다.
getCurrentPositionAsync와 getLastKnownPositionAsync 차이
const freshLocation = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.High,
});
const cachedLocation = await Location.getLastKnownPositionAsync({
maxAge: 1000 * 60 * 5,
requiredAccuracy: 200,
});
getCurrentPositionAsync는 말 그대로 지금 시점의 위치를 다시 잡아오려는 성격이 강합니다. 그래서 더 정확할 수 있지만 응답까지 시간이 걸릴 수 있습니다. 반면 getLastKnownPositionAsync는 기기에 이미 저장된 최근 위치를 가져오므로 더 빠를 수 있지만, 최신 위치가 아닐 수도 있습니다.
실무에서는 둘을 함께 쓰기도 합니다. 첫 화면 진입 시에는 getLastKnownPositionAsync로 빠르게 좌표를 먼저 보여주고, 그다음 getCurrentPositionAsync로 최신 위치를 다시 받아 갱신하는 방식입니다. 이 패턴은 체감 속도와 정확도를 함께 챙기기 좋아서 초심자가 알아두면 실전에 바로 써먹기 좋습니다.
실시간 위치 추적과 watchPositionAsync의 역할
현재 위치를 한 번 가져오는 것과 실시간 위치 추적은 완전히 다른 문제입니다. 사용자가 계속 이동하고 있고, 그 변화에 맞춰 화면도 계속 바뀌어야 한다면 watchPositionAsync를 사용해야 합니다. 이 메서드는 위치가 갱신될 때마다 콜백을 실행해 주는 구독 방식입니다.
여기서 중요한 포인트는 이 구독이 포그라운드 기준이라는 점입니다. 앱이 열려 있고 사용 중일 때 위치 변화를 받는 데 적합합니다. 앱이 뒤로 가거나 꺼진 상태까지 추적하고 싶다면 watchPositionAsync만으로는 부족하고, startLocationUpdatesAsync 같은 백그라운드용 흐름으로 넘어가야 합니다.
watchPositionAsync로 위치 변화 구독하기
import { useEffect, useRef, useState } from 'react';
import * as Location from 'expo-location';
export default function Tracker() {
const [coords, setCoords] = useState<Location.LocationObjectCoords | null>(null);
const subscriptionRef = useRef<Location.LocationSubscription | null>(null);
useEffect(() => {
const startWatch = async () => {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') return;
subscriptionRef.current = await Location.watchPositionAsync(
{
accuracy: Location.Accuracy.Balanced,
distanceInterval: 10,
timeInterval: 3000,
},
(location) => {
setCoords(location.coords);
}
);
};
startWatch();
return () => {
subscriptionRef.current?.remove();
};
}, []);
return null;
}
distanceInterval은 일정 거리 이상 이동했을 때 업데이트를 받도록 도와주고, timeInterval은 일정 시간 간격을 기준으로 업데이트 빈도를 조절할 때 사용합니다. 이 값들은 무조건 작게 잡는다고 좋은 것이 아닙니다. 너무 촘촘하면 배터리 소모가 커지고, 실제 화면에 필요하지 않은 잦은 업데이트가 발생할 수 있습니다.
또한 remove 정리는 꼭 필요합니다. 구독을 해제하지 않으면 화면이 사라진 뒤에도 불필요한 위치 업데이트가 계속 이어질 수 있고, 같은 구독이 여러 번 쌓이면서 성능과 동작 안정성에 문제가 생길 수 있습니다. 초심자는 위치 구독 시작보다 정리 시점을 함께 기억하는 편이 좋습니다.
예를 들어 러닝 기록처럼 이동 경로가 중요하면 좀 더 촘촘한 설정이 필요할 수 있습니다. 반면 가까운 매장 리스트를 갱신하는 정도라면 몇 초 또는 몇 미터 단위로 적당히 완화해도 충분합니다. 결국 위치 정확도와 업데이트 빈도는 기능 목적에 맞게 정하는 것이 핵심입니다.
보조 기능: 주소와 좌표를 서로 바꾸기
const geocoded = await Location.geocodeAsync('서울특별시 강남구 테헤란로 123');
const reversed = await Location.reverseGeocodeAsync({
latitude: 37.5665,
longitude: 126.978,
});
이 기능은 현재 위치 조회나 실시간 추적의 핵심 축이라기보다는, 위치 데이터를 더 사람이 이해하기 쉽게 바꿔주는 보조 기능에 가깝습니다. 지오코딩은 주소를 좌표로 바꾸는 기능이고, 리버스 지오코딩은 좌표를 주소 형태로 바꾸는 기능입니다.
예를 들어 사용자의 좌표만 화면에 보여주기보다, 현재 위치를 동네 이름이나 도로명 주소로 바꿔 보여주고 싶을 때 유용합니다. 다만 반복 호출이 많아지면 부담이 커질 수 있으므로, 검색창 입력마다 바로 호출하기보다 적절한 실행 시점을 두는 편이 안정적입니다. 또한 Android에서는 지오코딩과 리버스 지오코딩을 쓰기 전에 포그라운드 위치 권한이 먼저 필요하다는 점도 함께 기억해 두는 것이 좋습니다.
백그라운드 위치와 지오펜싱이 더 어려운 이유
![[React Native] Expo Location 사용법 – 현재 위치 조회, 실시간 추적, 백그라운드 위치까지 정리 2 ChatGPT Image 2026년 3월 10일 오후 06 21 19 1](https://blogflow.kr/wp-content/uploads/2026/03/ChatGPT-Image-2026년-3월-10일-오후-06_21_19-1-1024x683.png)
이 구간에서는 먼저 개념 차이를 짧게 잡고 가는 것이 중요합니다. 백그라운드 위치는 앱이 화면 뒤로 가도 계속 좌표를 받아오는 기능이고, 지오펜싱은 특정 반경에 들어오거나 나갈 때만 이벤트를 받는 기능입니다. 둘 다 백그라운드 성격을 가질 수 있지만, 해결하려는 문제는 같지 않습니다.
여기서부터는 난도가 분명히 올라갑니다. 백그라운드 위치는 앱이 화면 뒤로 가도 위치 이벤트를 받아야 하므로, 단순한 훅이나 컴포넌트 내부 로직만으로 끝나지 않습니다. TaskManager.defineTask를 최상위 범위에서 정의해야 하고, 플랫폼별 권한과 앱 설정도 갖춰져 있어야 합니다.
또 한 가지 중요한 사실은 앱이 완전히 종료된 뒤의 동작이 플랫폼 제약을 받는다는 점입니다. 사용자가 앱을 강제로 종료하면 백그라운드 위치는 멈출 수 있습니다. 안드로이드는 제조사 정책에 따라 최근 앱 목록에서 제거하는 행동이 곧 종료로 취급되기도 합니다. 그래서 백그라운드 위치는 코드만 맞다고 100% 동일하게 동작하는 기능이 아닙니다.
백그라운드 위치 추적의 기본 구조
import * as TaskManager from 'expo-task-manager';
import * as Location from 'expo-location';
const LOCATION_TASK_NAME = 'background-location-task';
TaskManager.defineTask(LOCATION_TASK_NAME, ({ data, error }) => {
if (error) {
return;
}
const { locations } = data as { locations: Location.LocationObject[] };
console.log('background locations', locations);
});
const startBackgroundTracking = async () => {
const foreground = await Location.requestForegroundPermissionsAsync();
if (foreground.status !== 'granted') return;
const background = await Location.requestBackgroundPermissionsAsync();
if (background.status !== 'granted') return;
await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, {
accuracy: Location.Accuracy.Balanced,
deferredUpdatesDistance: 50,
deferredUpdatesInterval: 10000,
foregroundService: {
notificationTitle: '위치 추적 중',
notificationBody: '앱이 백그라운드에서도 위치를 확인합니다.',
},
});
};
이 코드에서 가장 눈에 띄는 부분은 TaskManager.defineTask가 컴포넌트 바깥, 즉 최상위 범위에 있다는 점입니다. 그 이유는 백그라운드 작업이 특정 화면의 렌더링 생명주기에 묶이면 안 되기 때문입니다. 화면이 다시 렌더링되거나 사라져도 작업 등록 기준은 유지되어야 하므로, 최상위 범위에서 정의하는 구조가 필요합니다.
또한 foreground permission을 먼저 받고, 그다음 background permission을 요청하는 순서도 중요합니다. 특히 Android 11 이상에서는 requestBackgroundPermissionsAsync 호출 시 시스템 설정 화면으로 이동할 수 있으므로, 그 전에 왜 이 권한이 필요한지 Modal 같은 안내 UI로 설명하는 흐름이 권장됩니다.
지오펜싱은 어떤 상황에서 쓰는가
import { GeofencingEventType } from 'expo-location';
import * as TaskManager from 'expo-task-manager';
import * as Location from 'expo-location';
const GEOFENCE_TASK = 'geofence-task';
TaskManager.defineTask(GEOFENCE_TASK, ({ data, error }) => {
if (error) {
return;
}
const { eventType, region } = data as {
eventType: number;
region: Location.LocationRegion;
};
if (eventType === GeofencingEventType.Enter) {
console.log('진입', region.identifier);
}
if (eventType === GeofencingEventType.Exit) {
console.log('이탈', region.identifier);
}
});
await Location.startGeofencingAsync(GEOFENCE_TASK, [
{
identifier: 'office',
latitude: 37.5665,
longitude: 126.978,
radius: 100,
notifyOnEnter: true,
notifyOnExit: true,
},
]);
지오펜싱은 사용자의 현재 좌표를 계속 화면에 보여주는 기능이 아니라, 특정 반경 안에 들어왔는지 나갔는지를 감지하는 기능입니다. 예를 들어 회사 근처에 도착하면 체크 버튼을 보이게 하거나, 특정 매장 반경에 들어오면 쿠폰 알림을 준비하는 식의 기능에 어울립니다.
다만 지오펜싱도 플랫폼별 제한이 있습니다. 따라서 처음부터 너무 많은 지역을 한꺼번에 등록하기보다, 실제 서비스에서 꼭 필요한 핵심 영역만 관리하는 편이 좋습니다. 위치 기능은 정확성만이 아니라 배터리, 권한 피로도, 운영 안정성까지 함께 고려해야 합니다.
정리
Expo Location을 이해할 때 가장 중요한 기준은 기능의 목적입니다. 지금 위치가 한 번만 필요하면 getCurrentPositionAsync를 먼저 떠올리면 되고, 빠른 응답이 중요하면 getLastKnownPositionAsync를 함께 고려하면 됩니다. 앱을 사용하는 동안 위치 변화가 계속 필요하면 watchPositionAsync가 맞고, 앱이 뒤로 가도 추적해야 한다면 startLocationUpdatesAsync와 TaskManager 흐름으로 넘어가야 합니다.
즉, Expo Location은 하나의 위치 API가 아니라 여러 수준의 위치 문제를 해결하는 도구 묶음에 가깝습니다. 그래서 학습 순서도 한 번에 전부 붙잡기보다 현재 위치 조회부터 시작해서, 실시간 추적, 주소 변환, 백그라운드 위치, 지오펜싱 순으로 넓혀 가는 편이 훨씬 효율적입니다. Expo Location을 처음 다룰수록 메서드 이름보다 먼저 사용 목적과 권한 흐름을 이해하는 것이 중요합니다.
아래처럼 지금 내 앱이 어느 단계인지 체크해 보면 방향을 잡기 쉽습니다. 현재 날씨나 주변 매장처럼 한 번만 위치가 필요하다면 현재 위치 조회 단계입니다. 지도가 계속 따라 움직이거나 이동 경로를 기록해야 한다면 실시간 추적 단계입니다. 앱을 닫지 않았더라도 화면 뒤로 간 상태에서 계속 위치를 받아야 한다면 백그라운드 위치를 검토해야 합니다.
많이 받는 질문
Q. Expo Go에서 백그라운드 위치 추적이 바로 되지 않는 이유는 무엇인가요?
백그라운드 위치 기능은 Expo Go에서 바로 테스트할 수 없는 부분이 있습니다. background location은 development build가 필요합니다. 그래서 Expo Go에서 동작하지 않는다고 해서 코드가 틀렸다고 단정하면 안 됩니다.
Q. 현재 위치만 한 번 가져오려는데도 권한 요청이 꼭 필요한가요?
네, 필요합니다. 현재 위치를 읽는 행위 자체가 권한이 필요한 기능이기 때문입니다. 따라서 requestForegroundPermissionsAsync로 허용 여부를 먼저 확인한 뒤 getCurrentPositionAsync를 호출하는 흐름이 기본입니다.
Q. accuracy를 가장 높게 설정하면 항상 좋은 것 아닌가요?
항상 그렇지는 않습니다. 정확도가 높을수록 더 정밀한 위치를 받을 수 있지만, 응답 속도나 배터리 사용량 측면에서 비용이 커질 수 있습니다. 기능 목적이 단순한데도 최고 정확도만 고집하면 오히려 과한 설정이 될 수 있습니다.