이 예제는 xml 웹뷰 기준으로 작성되었습니다. Accompanist의 Compose WebView 쓰려고 보니까 xml 웹뷰를 감싸고 있는 Wrapper 형태더라고요. 그렇다면 굳이…? 🤔

참고 : Accompanist WebView Document


웹뷰를 표시할 화면 만들기

안드로이드 프로젝트를 하나 만들고 전체 화면을 웹뷰로 채워봅시다.

후루룩 만든 MainActivity.kt

class MainActivity : AppCompatActivity() {  
  
	private lateinit var webview: WebView  
	  
	override fun onCreate(savedInstanceState: Bundle?) {  
		super.onCreate(savedInstanceState)  
		setContentView(R.layout.activity_main)  
		webview = findViewById(R.id.webView)  
	}  
}

후루룩 만든 activity_main.xml

<?xml version="1.0" encoding="utf-8"?>  
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"  
	xmlns:app="http://schemas.android.com/apk/res-auto"  
	xmlns:tools="http://schemas.android.com/tools"  
	android:layout_width="match_parent"  
	android:layout_height="match_parent"  
	tools:context=".MainActivity">  
  
	<WebView  
		android:id="@+id/testWebView"  
		android:layout_width="match_parent"  
		android:layout_height="match_parent"  
		app:layout_constraintBottom_toBottomOf="parent"  
		app:layout_constraintEnd_toEndOf="parent"  
		app:layout_constraintStart_toStartOf="parent"  
		app:layout_constraintTop_toTopOf="parent" />  
  
</androidx.constraintlayout.widget.ConstraintLayout>

테스트에 필요한 웹뷰 준비하기 (웹뷰가 이미 있다면 패스)

저는 웹알못이라 이 예제에 쓸 웹페이지를 만들 지식이 없습니다. 대신 Chat GPT 가 있죠. ~~핑거 프린세스~


우리는 이 html 파일을 안드로이드 프로젝트 내에 넣고, 로컬에서 웹페이지를 띄워 테스트를 할 겁니다.

app/src/main 경로에서 우클릭 > New > Directory 를 통해 assets 디렉토리를 하나 만들어줍니다. 거기에 아무이름.html 파일을 만들고 내용을 작성해봅시다. 저는 index.html 로 만들었습니다.

<!DOCTYPE html>  
<html>  
<head>  
	<style>  
		body {  
			display: flex;  
			justify-content: center;  
			align-items: center;  
			height: 100vh;  
			flex-direction: column;  
			font-size: 24px; /* Adjust font size as needed */  
		}  
		button {  
			padding: 10px 20px;  
			font-size: 24px; /* Adjust font size as needed */  
			margin-top: 20px;  
		}  
	  
	</style>  
</head>  
<body>  
<p id="message">안녕 예나쓰</p>  
<button id="button">웹으로 데이터 보내기</button>  
  
<script>  
	document.getElementById("button").onclick = function() {  
		// Define action when button is clicked  
	}  
  
</script>  
</body>  
</html>

다시 MainActivity 로 돌아와 onCreate의 맨 아래에 해당 경로의 페이지를 여는 코드를 추가하고 앱을 빌드하면 폰에서 이 웹뷰를 확인할 수 있습니다.

//MainActivity.kt

webview.loadUrl("file:///android_asset/index.html")




웹뷰 기본 세팅 하기

이제 메인 액티비티로 가서 웹뷰에서 범용적으로 쓰이는 세팅 값들을 추가할 것입니다. 아래의 값이 전부 다 필요하진 않지만, 최소한 javaScriptEnabled = true 는 하도록 합시다.

MainActivity 에 웹뷰 기본 세팅을 설정하는 함수를 하나 만듭니다.

@SuppressLint("SetJavaScriptEnabled")  
private fun WebView.setDefaultSettings() {
	val webSettings = this.settings  
	  
	with(webSettings) {  
		// 자바스크립트를 활성화. 기본값 false// 워닝을 보기 싫으면 @SuppressLint("SetJavaScriptEnabled") 를 추가  
		javaScriptEnabled = true  
		  
		// WebView가 JavaScript의 window.open() 함수 호출을 허용. 기본값 false
		javaScriptCanOpenWindowsAutomatically = true  
		  
		// 뷰포트 메타태그를 지원. 기본값 false
		useWideViewPort = false
		  
		// 화면 크기에 맞춰 컨텐츠가 확대/축소되도록 함. 기본값 false
		loadWithOverviewMode = true  
		  
		// 화면에 맞추어 확대/축소. 기본값 true
		setSupportZoom(true)  
		  
		// 내장 줌 컨트롤을 사용할 수 있도록 함. 기본값 true
		builtInZoomControls = true  
		  
		// 줌 컨트롤을 화면에 표시하지 않음. 기본값 false
		displayZoomControls = false  
		  
		// 캐시 모드 설정. 기본값 LOAD_DEFAULT
		cacheMode = WebSettings.LOAD_NO_CACHE  
		  
		// 로컬 스토리지 활성화. 기본값 false
		domStorageEnabled = true  
	}  
  
}

웹뷰 인터페이스 붙이기

이제 웹뷰에 붙여줄 Interface를 만들어봅시다. 이 친구는 웹뷰에서 안드로이드 Native 함수를 호출할 수 있게 해주는 인터페이스 입니다. 이 인터페이스에 등록된 함수는 웹 페이지에서 직접적으로 호출할 수 있습니다. 이 인터페이스를 통해 “웹에서 모바일로” 데이터를 좀 보내볼 생각입니다.

  1. 주고 받을 데이터의 Wrapper class 만들기
  2. 웹 -> 모바일로 데이터를 받아서 처리하는 함수 만들기
  3. 통신을 위한 JavascriptInterface 만들기
  4. Activity에서 설정하고 로드하기

1. 주고 받을 데이터의 Wrapper class 만들기

먼저 Wrapper로 사용할 클래스를 하나 만들어줍니다. 그냥 String 형식으로 데이터를 주고 받아도 문제 없으나, 그것만으로는 모자랄걸…?? 이 예제처럼 웹 -> 모바일 통신 시 Int, String 값이 하나씩 필요하다고 칩니다.

data class WebViewData(  
	val intValue: Int,  
	val stringValue: String,  
)


2. 웹 -> 모바일로 데이터를 받아서 처리하는 함수 만들기

원래 안드로이드 예제에서 만만한게 토스트니 토스트를 띄워보는 것으로 하겠습니다.

private fun onReceiveJavascript(intValue: Int, stringValue: String) {  
	Toast.makeText(this, "int: $intValue, string: $stringValue", Toast.LENGTH_SHORT).show()  
}


3. 통신을 위한 JavascriptInterface 만들기

웹과 통신은 json으로 하고, 이 클래스로 파싱해서 사용할 것입니다. 저는 Gson을 사용했습니다. JavascriptInterface 라는 이름의 클래스를 만들었습니다.

이 클래스를 만들 때 (Int, String) -> Unit 람다를 인자로 받아 웹에서 데이터를 받아올 때 사용할 것입니다.

//JavascriptInterface.kt

import android.webkit.JavascriptInterface  
import com.google.gson.Gson  
import java.net.URLDecoder

class JavascriptInterface(val onReceiveJavascript: (Int, String) -> Unit) {  
	@JavascriptInterface  
	fun messageFromWebToMobile(json: String?) {  
		var jsonDecoded = ""  
		if (json == null) {  
			return  
		} else {  
			try {  
				jsonDecoded = URLDecoder.decode(json, "UTF-8")  
			} catch (e: Exception) {  
				e.printStackTrace()  
			}  
		}  
		val data = Gson().fromJson(jsonDecoded, WebViewData::class.java)  
		onReceiveJavascript(data.intValue, data.stringValue)  
	}  
}


4. Activity에서 설정하고 로드하기

javascriptInterface 객체를 하나 만들어주고 웹뷰에 추가합니다.

override fun onCreate(savedInstanceState: Bundle?) {  
	super.onCreate(savedInstanceState)  
	setContentView(R.layout.activity_main)  
	  
	val javascriptInterface = JavascriptInterface(::onReceiveJavascript)  
	  
	webview = findViewById<WebView?>(R.id.webView).apply {  
		setDefaultSettings()  
		addJavascriptInterface(javascriptInterface, NAME_YENA_WEB_VIEW)  
		loadUrl("file:///android_asset/index.html")  
	}  
}  
  
override fun onDestroy() {  
	webview.apply {  
		removeJavascriptInterface(NAME_YENA_WEB_VIEW)  
		destroy()  
	}  
	super.onDestroy()  
}


const val NAME_YENA_WEB_VIEW = "YenaWebView"


이대로 넣고 실행하면! 아무 일도 안 일어납니다. 웹뷰 수정을 안 했잖아… 저는 웹뷰 테스트를 위해 index.html 에 버튼 클릭 처리를 추가했습니다. 안드로이드에서 만든 JavascriptInterface name 값과 동일한 이름을 쓰셔야 작동합니다 (여기서는 YenaWebView)

<script>  
	document.getElementById("button").onclick = function() {  
	var jsonData = {  
		intValue: 1234,  
		stringValue: "테스트 그만하고 자라"  
	};  
	  
	var jsonString = JSON.stringify(jsonData);  
	// Call Android function  
	YenaWebView.messageFromWebToMobile(jsonString);  
	}  
</script>