Jetpack Compose WebView 사용 이슈정리
Android webview 를 구현함에 있어서 기존 방식(as-is)과 compose 에서 webview 사용방식(to-be)의 차이점 위주로 내용을 정리해봤습니다.
(2024–01–12)
- Accompanist Webview 같은 경우 Deprecated 되었습니다.
- Deprecated 된 이유는 별도의 커스텀이 필요하지 않다면, Accompanist Webview를 그대로 사용해도 좋지만, 그게 아니라면 fork하거나 생성해서 사용해야합니다.
의존성 추가
Compose 용 WebView를 구현하기 위해선 아래 라이브러리 추가가 필요합니다.
(app/build.gradle) 작성 당시 0.24.13-rc가 최신이었으나 계속 업데이트 되고 있기 때문에 최신버전을 권장드립니다.
dependencies {
implementation "com.google.accompanist:accompanist-webview:0.24.13-rc"
}
WebView 비교해보기
- 기존 loadUrl 대체
//as-is
binding.webview.loadUrl("www.naver.com")//to-be
val webViewState =
rememberWebViewState(
url = "www.naver.com",
additionalHttpHeaders = emptyMap()
)WebView(state = webViewState)
webViewState안에서 url과 additionalHttpHeaders를 추가해 줄 수 있는데,
로드하고자 하는 Url과 HttpHeaders(AccessToken 등) 또한 설정할 수 있습니다.
- 기존 WebClient, ChromeClient 대체
//as-is
binding.webview.webViewClient = WebViewClient()
binding.webview.webChromeClient = WebChromeClient()//to-be
private val webViewClient = AccompanistWebViewClient()
private val webChromeClient = AccompanistWebChromeClient()WebView(
state = webViewState,
client = webViewClient,
chromeClient = webChromeClient
)
기존에 사용하던 WebViewClient, WebChromeClient을 사용하는 것이 아닌 각각 AccompanistWebViewClient, AccompanistWebChromeClient을 사용해야 합니다. 단 Accompanist 용 Client는 Composable이 아니기 때문에 전역변수로 선언해놓고 사용하는것이 좋을 것 같습니다.
- 기존 WebView Setting 대체
//as-is
webview.settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
javaScriptCanOpenWindowsAutomatically = false
}//to-be
WebView(
state = webViewState,
client = webViewClient,
chromeClient = webChromeClient,
onCreated = { webView ->
with(webView) {
settings.run {
javaScriptEnabled = true
domStorageEnabled = true
javaScriptCanOpenWindowsAutomatically = false
}
}
}
)
WebView의 onCreated는 WebView가 처음 생성될 때 호출됩니다. 위와 같은 웹뷰 추가 설정을 하려면 Composable WebView의 onCreated 인자값으로 람다를 넘겨서 설정해야합니다. 또한 공식문서에선 WebViewClient와 WebChromeClient는 onCreated 람다가 호출된 후에 client가 설정되므로 내부에서 설정하지 말라고 명시되어 있습니다.
- 기존 WebView 뒤로가기 제어
//as-is
override fun onBackPressed() { // Activity 기준
if(binding.webView.canGoBack()) {
binding.webView.goBack()
} else {
super.onBackPressed()
}
}//to-be
val webViewNavigator = rememberWebViewNavigator()WebView(
state = webViewState,
navigator = webViewNavigator,
client = webViewClient,
chromeClient = webChromeClient,
onCreated = onCreated
)
BackHandler(enabled = true) {
if (webViewNavigator.canGoBack) {
webViewNavigator.navigateBack()
} else {
findNavController().popBackStack() // 프로젝트에 맞게 사용
}
}
기존 Activity나 Fragment를 사용하는 경우 onBackPressed 또는 OnBackPressedCallback을 구현해 내부에서 WebView에 대한 동작을 처리했었습니다.
Composable WebView에서는 webViewNavigator를 생성해 BackHandler Composable 안에서 WebView에 대한 동작을 처리해야 합니다.
BackHandler Composable은 시스템 뒤로가기 버튼을 눌렀을때 동작을 정의 할 수 있는 Composable입니다. 컴포저블에서 BackHandler를 사용하면LocalOnBackPressedDispatcherOwner 의 OnBackPressedDispatcher 에 추가됩니다.
// WebView Composable 내부 코드BackHandler(captureBackPresses && navigator.canGoBack) {
webView?.goBack()
}
WebView Composable 내부에는 BackHandler가 추가되어있어, 필요에 따라 별도로 구현해주시면 됩니다.
- WebView 브릿지
onCreated = { webView ->
with(webView) {
settings.run {
javaScriptEnabled = true
domStorageEnabled = true
javaScriptCanOpenWindowsAutomatically = false
}
addJavascriptInterface(Bridge(), "Bridge")
}
}
브릿지 같은 경우에는 Composable 내 onCreated에서 설정이 가능합니다.
- State 중요 참고사항
추가로 state같은 경우 remember를 사용할 경우 Configuration Change가 발생 했을 경우 초기화 될 수 있기 때문에 rememberSavable을 사용해 Bundle에 저장하여 상태를 저장할 수 있습니다.
다만 Activity에 상태를 저장하는 것은 이상적이지 않으므로 ViewModel을 사용하는 것이 UI와 상태를 분리할 수 있는 장점이 있고, remember를 사용하지 않고도 상태를 유지할 수 있기 때문에 ViewModel 내에서 State를 구현하는 것을 추천드립니다.
class WebViewViewModel:ViewModel() {
val webViewState = WebViewState(
WebContent.Url(
url = "www.naver.com",
additionalHttpHeaders = emptyMap()
)
)
val webViewNavigator = WebViewNavigator(viewModelScope)
}...// MainActivity.ktsetContent {
val webViewState = viewModel.webViewState
val webViewNavigator = viewModel.webViewNavigator
댓글
댓글 쓰기