일주일 동안 개고생하면서 저만의 방식으로 웹뷰에서 위치 서비스를 구현했습니다. 제가 서칭 능력이 부족했을 수도 있지만 웹뷰로 위치 서비스를 처음부터 끝까지 구현해 낸 자료를 찾기 어려웠습니다. 리액트 네이티브와 웹뷰 간의 통신, 모바일에서 위치 권한 얻는 법, 위치 정보 가져오기, 권한 요청 거부 시 재요청과 재요청에 따른 위치 정보 가져오기... 정말 다양하게 찾아보고 적용해 보고 수정한 것 같습니다.
리액트 네이티브와 웹뷰 간의 통신이 추가되니 정말 어지럽습니다. 계속해서 원리나 과정을 확인해 가면서 구현하다 보니 시간이 더 오래 걸린 것 같습니다. 마치 손에 모래주머니를 차고 코드를 치면서 수련하는 기분입니다. 이 프로젝트를 온전히 잘 끝낸다면 웹 서비스쯤은 한 손으로 구현할 수 있지 않을까 싶습니다.(농담)
한편으론 부족한 점이 많다고 생각이 들지만 계속해서 코드를 치고 회고하는 과정에서 조금씩 성장하는 느낌이 좋습니다.
중점은 위치 서비스 Flow에 따른 기능 구현 같습니다.
자세한 내용들은 생략해 가면서 Flow를 중점으로 정리해 보도록 하겠습니다.
useEffect(() => {
if (isApp()) {
window.ReactNativeWebView?.postMessage(
JSON.stringify({ type: 'LOCATION_PERMISSION' }),
);
}
}, []);
const locationPermit = async (
e: WebViewMessageEvent,
setGeoLocatonAccess: Dispatch<SetStateAction<boolean>>,
) => {
const nativeEvent = JSON.parse(e.nativeEvent.data);
if (nativeEvent.type === 'LOCATION_PERMISSION') {
if (Platform.OS === 'android') {
try {
const garanted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
);
if (garanted === PermissionsAndroid.RESULTS.GRANTED) {
setGeoLocatonAccess(true);
}
} catch (err) {
console.error(err);
}
}
}
};
const WebViewcontainer = () => {
const webViewRef = useRef<WebView>(null);
const [geoLocation, setGeoLocation] = useState({latitude: 0, longitude: 0});
const [geoLocationAccess, setGeoLocatonAccess] = useState(false);
webViewRef.current?.postMessage(JSON.stringify(geoLocation));
useEffect(() => {
if (geoLocationAccess) {
Geolocation.watchPosition(
position => {
const {latitude, longitude} = position.coords;
setGeoLocation({latitude, longitude});
},
err => {
console.error(err);
},
{
enableHighAccuracy: true,
},
);
}
}, [geoLocationAccess]);
return (
<WebView
ref={webViewRef}
source={{uri: homeIp}}
onMessage={(e: WebViewMessageEvent) => {
locationPermit(e, setGeoLocatonAccess);
}}
/>
);
};
const useGeolocation = () => {
const [geolocation, setGeolocation] = useState({
latitude: 0,
longitude: 0,
});
const [geolocationAccess, setGeolocationAccess] = useState(false);
useEffect(() => {
if (isApp()) {
const location = (e: MessageEvent<string>) => {
const data: { latitude: number; longitude: number } = JSON.parse(
e.data,
);
if (data.latitude !== 0 && data.longitude !== 0) {
setGeolocation(data);
setGeolocationAccess(true);
}
};
document.addEventListener('message', location);
return () => {
document.removeEventListener('message', location);
};
}
}, []);
return { geolocation, geolocationAccess };
};
const useReverseLocation = (geolocation: reverseLocation) => {
const dispatch = useDispatch();
useEffect(() => {
const locationRequest = async () => {
try {
const result = await axios.get(
`https://dapi.kakao.com/v2/local/geo/coord2address.json?x=${geolocation.longitude}&y=${geolocation.latitude}`,
{
headers: {
Authorization: `KakaoAK ${process.env.REACT_APP_KAKAO_REST_API_KEY}`,
},
},
);
const region = translateLocationKorToEng(
result?.data?.documents[0]?.address?.region_1depth_name as string,
);
const locationKor = translateLocationEngToKor(region);
dispatch(locationActions.setLocation(locationKor));
} catch (err) {
console.error(err);
}
};
void locationRequest();
}, [geolocation]);
};
const BtnHandler = () => {
if (!geolocationAccess) {
if (isApp()) {
window.ReactNativeWebView?.postMessage(
JSON.stringify({ type: 'LOCATION_PERMISSION_RETRY' }),
);
return;
}
}
navigate('/home/nearby');
};
const locationPermitRetry = async (
e: WebViewMessageEvent,
setGeoLcationReAccess: Dispatch<SetStateAction<boolean>>,
geoLocationAccess: boolean,
) => {
const nativeEvent = JSON.parse(e.nativeEvent.data);
if (Platform.OS === 'android') {
if (nativeEvent.type === 'LOCATION_PERMISSION_RETRY') {
if (geoLocationAccess) {
Alert.alert(
'위치 정보를 가져오는 중입니다.',
'잠시 후 다시 시도해 주세요',
);
return;
}
Alert.alert(
'위치 액세스 권한이 필요합니다.',
'권한 허용 후 이용해 주시기 바랍니다.',
[
{
text: '취소',
onPress: () => {},
style: 'cancel',
},
{
text: '설정으로 이동',
onPress: () =>
Linking.openSettings().then(() => setGeoLcationReAccess(true)),
},
],
{cancelable: false},
);
}
}
};
const [geoLocationAccess, setGeoLocatonAccess] = useState(false);
const [geoLcationReAccess, setGeoLcationReAccess] = useState(false);
const appState = useRef(AppState.currentState);
useEffect(() => {
const subscription = AppState.addEventListener('change', nextAppState => {
if (appState.current.match(/background/) && nextAppState === 'active') {
if (geoLcationReAccess) {
setGeoLocatonAccess(true);
}
if (geoLocationAccess) {
findLocation(setGeoLocation);
}
}
appState.current = nextAppState;
});
return () => {
subscription.remove();
};
}, [geoLocationAccess, geoLcationReAccess]);
