하나의 앱에 여러 서비스가 동시에 있을 때 자연스러운 하나의 OneApp or Super App으로써 동작되고 각각의 유지보수와 업데이트를 위해서 모바일 앱에선 여러 서비스들을 웹뷰로 취급하고 관리하기도 합니다.
하나의 Webview에서 여러 웹 서비스들의 링크를 클릭하여 이동할 때 현재의 도메인에서 벗어나 새롭게 링크의 도메인으로 Webview가 새로고침되면서 이동을 하게 됩니다.
문제는 하나의 Webview에서 이동하는 것이므로 이동되기 전의 서비스에서 저장된 모든 정보가 사라지며 다시 뒤로가기로 돌아 왔을 때 현재의 화면과 정보를 유지하지 못하여 다시 모든 초기화와 복구 프로세스가 실행되어야 한다는 것입니다.
때문에 새로운 웹 서비스를 띄울 때 기존 화면과 정보는 유지한채 새로운 Webview Stack으로 쌓고 자연스럽게 뒤로가기로 복구되어야 합니다.
외부 서비스의 경우엔 InApp Browser든지 DeepLink를 이용하여 띄우면 되지만 서비스 경험을 유지시키고 자연스런 자체 서비스 전환을 위해선 사용할 수 없습니다.
Multi Webview 스택 쌓이는 과정과 돌아가는 예
아래와 같이 현재 3개의 서로 다른 웹 서비스(도메인)가 모바일 앱에서 하나의 앱처럼 동작되고 있습니다.
최종 결과 영상
구현 방법
Navigation
React Native에서 많이 사용하는 네비게이션 라이브러리인 React Navigation을 사용하여 모바일 앱의 홈과 새롭게 항상 띄울 Webview Stack을 선언합니다.
이 WebviewScreen에는 공통된 메시지 로직이 담길 수도 있지만 각 도메인마다 다른 로직이나 메시지를 담아야 한다면 그에 따른 다른 스택을 새롭게 정의할 수도 있습니다.
function Navigation() {
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName={ROUTE.HOME}
screenOptions={{headerShown: false, animation: 'none'}}>
<Stack.Screen name={ROUTE.HOME} component={HomeScreen} />
<Stack.Screen name={ROUTE.WEBVIEW} component={WebviewScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
이후 링크(외부 웹 서비스)를 클릭하여 리소스 요청 시 스택으로 쌓고자 하는 화이트 리스트 서비스(도메인)가 포함된 경로일 경우 새로운 Webview Stack으로 띄우고 화면을 쌓습니다.
function onShouldStartLoadWithRequest(e: {url: string}) {
let wurl = e.url;
let rs = true;
if (
!wurl.startsWith('http://') &&
!wurl.startsWith('https://') &&
!wurl.startsWith('javascript:')
) {
... // depp link 처리
} else if (
!wurl.startsWith(appState.webViewUri) && // 현재 실행되고 있는 서비스 경로가 아니고
wurl.startsWith('https://white-list-domain.com') // 화이트 리스트에 있는 서비스일 경우
) {
// 새로운 wurl을 웹뷰에 띄우기 위한 스택을 실행시킨다.
navigation.push(ROUTE.WEBVIEW, {uri: wurl});
rs = false;
}
return rs;
}
웹에서 Multi Webview 지원 확인(뒤로가기 판단하기 위한 로직이 필요)
하나의 Webview를 사용한다면 뒤로가기에 대해선 브라우저 뒤로가기로 자연스럽게 히스토리를 복구할 수 있으므로 별도 React Native가 처리할 내용이 크게 없습니다.
하지만, multiwebview를 지원하는 경우엔 뒤로가기에 대한 처리를 Webview에 맡기는 것이 아니라 React Native에서 뒤로가기를 제어해야 합니다. 화면에 쌓인 스택을 정리하거나 다른 로직을 처리하기 위해서 입니다.
React Native에선 WebView에 UserAgent를 넣을 수 있으며 이를 웹 서비스에서 확인할 수 있습니다. version이나 feature와 같은 여러 정보들을 같이 제공할 수 있으므로 해당 서비스가 React Native에서 띄운 것인지를 확인하는데 도움이 될 수 있습니다.
지원하지 않는 경우엔 기존과 동일하게 처리(브라우저 뒤로가기로 모두 처리)해야 하므로 뒤로가기 기능을 구현하기 위해 필요합니다.
<WebView
...
applicationNameForUserAgent={`MobileApp/${getVersion()}(${feature.join(',')})`}
...
onNavigationStateChange={event => {
setCanGoBack(_ => event.canGoBack);
}}
/>
웹 서비스에서는 현재 모바일 Webview에서 실행되었는지 그리고 해당 앱이 MultiWebview를 지원하는지 확인하여 뒤로가기에 대한 처리를 브라우저 뒤로가기로 처리할지 Native에 메시지를 전달하여 뒤로가기를 수행할지 결정해야 합니다.
function isGoBackSupport() {
return navigator.userAgent.includes('MobileApp') &&
navigator.userAgent.includes('multiwebview')
}
웹에서 뒤로가기 처리
웹에서 GO_BACK
메시지를 보내 뒤로가기를 처리하면 React Native에서 뒤로가기(웹+네이티브 스택 뒤로가기/정리)를 처리합니다.
// web
window.ReactNativeWebView.postMessage(JSON.stringify({ type: "GO_BACK" }));
// RN message handler
async function onMessage(event: any) {
...
switch (data.type) {
...
case 'GO_BACK': {
onPressBackButton();
}
...
}
}
디바이스 뒤로가기 버튼 처리
Webview 뒤로가기 처리뿐 아니라 현재 Webview Stack이 종료되어야 할 경우를 위해서 navigation.canGoBack()
여부를 확인하고 웹뷰 스택을 정리하는 부분을 추가해야 합니다.
function onPressBackButton() {
if (canGoBack) { // Webview의 onNavigationStateChange에서 뒤로갈 수 있는지 여부 확인
webViewRef.current?.goBack();
return true;
} else if (navigation.canGoBack()) { // 추가된 부분: 현재 스택을 정리하고 이전 스택으로 이동
navigation.goBack();
return true;
}
return false;
}
결론
각 팀별 또는 스쿼드별 목적조직으로서 별도의 서비스를 개발하지만 하나의 모바일 앱에 모든 서비스들을 유기적으로 연결하기 위해서 사용하는 방법 중 하나가 Webview를 이용한 서비스 제공 입니다.
이 때 하나의 Webview를 사용하면 공통 로직이나 메시지를 정의하고 여러 서비스를 제공할 수 있지만 이전 서비스를 다시 불러오면서 발생되는 초기화 및 복구 작업들이 들어가게 됩니다.
MultiWebview 구현을 통해 자연스러운 화면 복구와 사용자 경험을 취하는 것이 좀 더 효율적으로 여러 서비스들을 사용하는 방법일 것입니다.