이번 글에서는 앱과 웹뷰의 통신에 대해 간단하게 정리보려고 한다 !
웹뷰가 무엇인지, 앱과 웹뷰가 통신을 어떻게 할 수 있는지에 대해 알아보자
1. 웹뷰(WebView)란 ?
웹뷰란 네이티브 앱에 내장되어 있는 웹 브라우저 이다.
https://docs.tosspayments.com/resources/glossary/webview
간단하게 말하면, 웹뷰란 것을 통해 App에서 웹 페이지를 보여줄 수 있다 !
(네이티브 앱과 웹 앱을 합친 앱을 하이브리드 앱이라고 한다)
그러면 네이티브 앱으로 다 구현하면 될걸, 왜 웹뷰를 사용하는 걸까?
크게 3가지 이유가 있다.
- 앱으로 구현하는 것보다 웹뷰로 구현하는 게 더 간편한 경우
- 앱의 배포 없이 업데이트 빠르게 하기 위해
- 여러 플랫폼에서 자유롭게 사용하기 위해(Android, ios 등)
하지만 웹뷰는 앱보다는 비교적 느리고, UI가 제한적인 것 등의 단점들이 있다.
2. 웹뷰와 앱의 통신 (Android기준)
웹뷰를 개발하다보면 앱의 기능을 웹뷰에서 사용해야 할 때(혹은, 앱과 데이터를 주고받아야 할 경우)가 있다
이런 경우에 앱에서 JavascriptInterface를 통해서 웹과 앱이 상호작용할 수 있다.
# 1. 앱에서 정의한 함수를 웹뷰에서 호출하는 경우
예를 들어, 앱에서 띄우는 Toast나 Alert 등을 웹뷰에서 띄워야 할 경우가 있다.
이 경우에는 앱에서 토스트를 띄우는 함수를 정의하고 웹에서 해당 함수를 호출하는 과정이 필요하다.
Android developer 에 나온 예시로 어떻게 통신을 하는지 살펴보자
https://developer.android.com/develop/ui/views/layout/webapps/webview?hl=ko#UsingJavaScript
(자세한 과정은 위 링크 참고)
1) Android에서 WebView 사용 설정 후 웹앱 인터페이스 생성
/** Instantiate the interface and set the context. */
class WebAppInterface(private val mContext: Context) {
/** Show a toast from the web page. */
@JavascriptInterface
fun showToast(toast: String) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show()
}
}
2)웹뷰에 JsInterface 사용설정 추가
WebView webView = (WebView) findViewById(R.id.webview);
webView.addJavascriptInterface(new WebAppInterface(this), "Android");
* Android(이름 자유)라는 이름으로 사용할 수 있게 함
3) 웹뷰에서는 "Android"라는 전역 객체에 접근해 showToast 호출
window.Android.showToast("토스트를 띄워줘!"); // Android.showToast("토스트를 띄워줘!");
* Android가 전역 객체로 들어가기에 window라고 굳이 안붙여도 된다.
# 2. 웹뷰에서 정의한 함수를 앱에서 호출하는 경우
예를 들어, 사용자가 입력한 텍스트를 앱에게 전달해야 한다고 가정해보자(혹은 반대)
이 경우에는 웹에서 함수를 선언하고 앱에서 해당 함수를 호출하여 데이터를 받는(or 주는) 구조다.
Andorid에서는 evaluateJavascript라는 메서드로 웹뷰에서 js 코드를 실행시킬 수 있다.
WebView.evaluateJavascript(script: String, resultCallback: ValueCallback<String>);
script엔 실행시킬 자바스크립트 코드를, resultCallback에는 해당 코드를 실행시킨 후 반환 값을 이용하는 콜백함수를 넣으면 된다.
(반환값을 이용할 일이 없으면 null)
1) WebView -> App
예시로, 아래와 같은 메서드가 웹뷰에 선언되어 있다고 할 때,
let message = "전달하고 싶은 메시지";
window.postMessage = () => {
return message;
}
네이티브에서 evaluateJavascript를 이용해 해당 message를 받을 수 있다.
WebView.evaluateJavascript('javascript:window.postMessage()', (message) -> {
// message를 이용한 코드
});
2) App -> WebView
웹뷰에서 아래와 같이 함수를 선언되어 있을 때,
function getMessageFromApp() {
return new Promise((resolve) => {
window.getMessage = (message) => {
resolve(message);
}
window.getMessage = undefined;
}
}
async function someFunc() {
const message = await getMessageFromApp();
}
앱에서 아래 코드를 실행시키면 웹뷰에서 데이터를 받을 수 있다.
WebView.evaluateJavascript("javascript:window.getMessage('보낼 메시지')", null);
* 순서 주의: 웹뷰에서 함수가 미리 선언이 되어있어야 함
이런 단방향의 통신(WebView -> App, App -> WebView)은 비교적 간단하다.
웹뷰와 앱이 서로의 함수를 호출하는 양방향 통신에 대해 좀 더 살펴보자
# 3. 웹뷰와 앱의 양방향 통신(WebView <-> App)
예를 들어, 앱에서 띄우는 Confirm 팝업을 웹뷰에서 띄우고 싶다고 생각해보자.
그러면 WebView에서는 팝업을 띄우는 함수를 호출하고, App에서는 해당 팝업을 띄운다.
그 다음 사용자의 취소/삭제 선택 결과를 App에서 WebView로 전달을 해줘야 한다.
이 경우에는 다음과 같은 과정이 필요하다
- A: 앱에서 팝업을 띄우는 함수(혹은 무언가..)를 선언
- B: 웹뷰에서 팝업결과를 받을 함수(혹은 무언가..)를 선언
- 웹뷰에서 팝업을 띄우는 함수(A)를 호출 시 앱에서 웹뷰의 팝업결과를 받는 함수(B)를 호출하도록 선언
웹뷰 관점에서 팝업선택결과를 받을 방법으로 두가지 방법이 있다.
1. resultCallback을 이용
사용자의 응답을 받을 수있는 또다른 resultCallback을 웹에서 정의하여 앱이 해당 함수를 호출했을 때 웹뷰가 데이터를 받을 수 있게한다.
1) 앱(App)에서 openConfirmPopup을 띄우는 함수를 정의
* ConfirmPopup(title, desc, resultCallback) 클래스가 정의되어있다고 가정
class WebAppInterface(private val mContext: Context) {
@JavascriptInterface
fun openConfirmPopup(title: String, desc: String, resultCallbackName: String) {
ConfirmPopup.open(title, desc, (result) -> {
// InterfaceName는 웹뷰에서 자유롭게 정의한다.
val script = "javacript:window.WebView.${resultCallbackName}(result)"
webView.evaluateJavascript(script, null);
});
}
}
evaluateJavacript라는 함수로 띄운 웹뷰에서 자바스크립트 코드를 실행할 수 있게 한다.
2) 웹에서 resultCallback을 정의 후 openConfirmPopup 실행
promise를 반환하는 함수를 선언하여, Android.getConfirmResult가 실행됐을 때 결과를 받을 수 있게 한다.
function openConfirmPopup(title, desc) {
return new Promise((resolve) => {
// getConfirmResult이라는 함수가 호출됐을 때 결과를 받을 수 있게함
window.WebView.getConfirmResult = (result) => {
resolve(result);
}
window.WebView.getConfirmResult = undefined;
window.Android.openConfirmPopup(title, desc, "getConfirmResult");
});
}
async function someFunction () {
const result = await opneConfirmPopup("정말로 삭제하시겠습니까?", "삭제된 게시글은 복구할 수 없습니다.");
}
두 번째 방법으로 CustemEvent를 이용하는 방법이 있다.
2. WebView에서 CustomEvent 정의
해당 방식은 웹뷰에서 CustomEvent를 정의하고 네이티브에서 실행시키는 방식이다.
let result = '' // 미리 result값을 받을 변수를 선언해둔다.
function openConfirmPopupCallback(event) {
result = event.result; // event에 응답 결과를 받을 수 있게 한다.
};
window.addEventListener("ConfirmPopup", openConfirmPopupCallback);
// window.removeEventListener("ConfirmPopup", openConfirmPopupCallback); 도 해줘야 한다.
function openConfirmPopup(title, desc) {
result = undefiend; // 이전 값을 초기화
window.Android.openConfirmPopup(title, desc);
return result;
}
* 커스텀 이벤트를 사용하기위해 윈도우에 이벤트를 걸어주었다.
그러면 앱쪽에서는 해당 이벤트를 dispatch 해줌으로써 openConfirmPopupCallback함수를 실행시킬 수 있다.
class WebAppInterface(private val mContext: Context) {
@JavascriptInterface
fun openConfirmPopup(title: String, desc: String) {
ConfirmPopup.open(title, desc, (result) -> {
val script = "javascript:window.dispatchEvent(new CustomEvent('ConfirmPopup', {result: result})"
webView.evaluateJavascript(script, null);
});
}
}
순서는 아래와 같다.
1. 결과 값을 받을 변수(react면 state) 을 선언
2. 결과 값을 받을 함수를 콜백으로 받는 event를 dom요소에 걸어둔다.
3. 네이티브에서 해당 이벤트를 dispatch하여 결과값을 넘김
4. 웹뷰에서 result를 받음
위와 같은 방식 이외에도 Android의 JSInterface를 이용해 다양한 방법으로 통신할 수 있을 것이다.
대표적인 방식으로 2가지를 소개해봤는데, 용도에 맞게 잘 활용하면 좋을 것 같다.
이번 글은 여기서 마무리!!!!!
궁금한 점이나 틀린 내용있으면 댓글로 달아주세요 ~
참고 자료
https://docs.tosspayments.com/resources/glossary/webview
https://developer.android.com/develop/ui/views/layout/webapps/webview?hl=ko
'Frontend > Article' 카테고리의 다른 글
딥링크(Deep Link)에 관하여 (Feat: URL Scheme, App Link, Universal Link) (0) | 2024.12.01 |
---|---|
CRA없이 React, Ts, Webpack 개발 환경 구축(+ eslint, prettier) (1) | 2024.06.06 |
Rollup을 이용하여 컴포넌트 라이브러리 만들기 ! (React, Ts, Sass, Storybook) (5) | 2023.11.26 |
rollup-plugin-postcss의 SASS 파일 내 custom alias 처리 문제 (0) | 2023.10.14 |
Style Dictionary 사용 방법 정리(feat. 피그마 디자인 토큰 가공하기) (0) | 2023.09.23 |