[응용] 카카오 주소검색 API (6)

쓰리원·2022년 7월 3일
1

안드로이드 지도API

목록 보기
8/12
post-thumbnail

1. 서버단에서 설정 (구글링시 예제가 많음)

서버에 html 파일을 업로드 해야합니다. 서버를 빌드하는 방법이 다양하므로 지금은 생략하겠습니다. 추후에 작성하겠습니다.

1. 서버에 업로드할 html

<!DOCTYPE html>
<html>
<head>
</head>
<body>
<!-- HELLO DAUM! -->

<!-- iOS에서는 position:fixed 버그가 있음, 적용하는 사이트에 맞게 position:absolute 등을 이용하여 top,left값 조정 필요 -->
<div id="layer" style="display:block;position:fixed;overflow:hidden;z-index:1;-webkit-overflow-scrolling:touch;">

</div>


<script src="https://t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<!-- <script src="http://dmaps.daum.net/map_js_init/postcode.v2.js"></script> -->
<script>


window.addEventListener("message", onReceivedPostMessage, false);

function onReceivedPostMessage(event){
    //..ex deconstruct event into action & params
    var action = event.data.action;
    var params = event.data.params;

    console.log("onReceivedPostMessage "+event);

}

function onReceivedActivityMessageViaJavascriptInterface(json){
     //..ex deconstruct data into action & params
     var data = JSON.parse(json);
     var action = data.action;
     var params = data.params;
       console.log("onReceivedActivityMessageViaJavascriptInterface "+event);
}


    // 우편번호 찾기 화면을 넣을 element
    var element_layer = document.getElementById('layer');

    function sample2_execDaumPostcode() {
        new daum.Postcode({
            oncomplete: function(data) {
			
                // 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.

                // 각 주소의 노출 규칙에 따라 주소를 조합한다.
                // 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
                var fullAddr = data.address; // 최종 주소 변수
                var extraAddr = ''; // 조합형 주소 변수

                // 기본 주소가 도로명 타입일때 조합한다.
                if(data.addressType === 'R'){
                    //법정동명이 있을 경우 추가한다.
                    if(data.bname !== ''){
                        extraAddr += data.bname;
                    }
                    // 건물명이 있을 경우 추가한다.
                    if(data.buildingName !== ''){
                        extraAddr += (extraAddr !== '' ? ', ' + data.buildingName : data.buildingName);
                    }
                    // 조합형주소의 유무에 따라 양쪽에 괄호를 추가하여 최종 주소를 만든다.
                    fullAddr += (extraAddr !== '' ? ' ('+ extraAddr +')' : '');
                }
				
				
				var fullRoadAddr = data.roadAddress; // 도로명 주소 변수
                var extraRoadAddr = ''; // 도로명 조합형 주소 변수

                // 법정동명이 있을 경우 추가한다. (법정리는 제외)
                // 법정동의 경우 마지막 문자가 "동/로/가"로 끝난다.
                if(data.bname !== '' && /[동|로|가]$/g.test(data.bname)){
                    extraRoadAddr += data.bname;
                }

                // 건물명이 있고, 공동주택일 경우 추가한다.
                if(data.buildingName !== '' && data.apartment === 'Y'){
                   extraRoadAddr += (extraRoadAddr !== '' ? ', ' + data.buildingName : data.buildingName);
                }

                // 도로명, 지번 조합형 주소가 있을 경우, 괄호까지 추가한 최종 문자열을 만든다.
                if(extraRoadAddr !== ''){
                    extraRoadAddr = ' (' + extraRoadAddr + ')';
                }
                // 도로명, 지번 주소의 유무에 따라 해당 조합형 주소를 추가한다.
                if(fullRoadAddr !== ''){
                    fullRoadAddr += extraRoadAddr;
                }				

				window.Android.processDATA(fullRoadAddr); // data.zonecode + ", " + 
            },
            width : '100%',
            height : '100%'
        }).embed(element_layer);

        // iframe을 넣은 element를 보이게 한다.
        element_layer.style.display = 'block';

        // iframe을 넣은 element의 위치를 화면의 가운데로 이동시킨다.
        initLayerPosition();
		
		
    }

    // 브라우저의 크기 변경에 따라 레이어를 가운데로 이동시키고자 하실때에는
    // resize이벤트나, orientationchange이벤트를 이용하여 값이 변경될때마다 아래 함수를 실행 시켜 주시거나,
    // 직접 element_layer의 top,left값을 수정해 주시면 됩니다.
    function initLayerPosition(){
        var width = (window.innerWidth || document.documentElement.clientWidth); //우편번호서비스가 들어갈 element의 width
        var height = (window.innerHeight || document.documentElement.clientHeight); //우편번호서비스가 들어갈 element의 height
        var borderWidth = 5; //샘플에서 사용하는 border의 두께

        // 위에서 선언한 값들을 실제 element에 넣는다.
        element_layer.style.width = width + 'px';
        element_layer.style.height = height + 'px';
        element_layer.style.border = borderWidth + 'px solid';
        // 실행되는 순간의 화면 너비와 높이 값을 가져와서 중앙에 뜰 수 있도록 위치를 계산한다.
        element_layer.style.left = (((window.innerWidth || document.documentElement.clientWidth) - width)/2 - borderWidth) + 'px';
        element_layer.style.top = (((window.innerHeight || document.documentElement.clientHeight) - height)/2 - borderWidth) + 'px';
    }

</script>

</body>
</html>

서버에 올라갈 html 코드 입니다.

2. MainActivity.kt

class MainActivity : AppCompatActivity() {

    private var et_address: EditText? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        et_address = findViewById(R.id.et_address)
        val btn_search = findViewById<Button>(R.id.button)

        btn_search.setOnClickListener {
            val intent = Intent(this@MainActivity, WebViewActivity::class.java)
            getSearchResult.launch(intent)
        }
    }

    private val getSearchResult =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult())
        { results ->
            if(results.resultCode == RESULT_OK) {
                if(results.data != null) {
                    val data = results.data!!.getStringExtra("data")
                    et_address?.setText(data)
                }
            }
        }
}

registerForActivityResult에 reusltCode 가 RESULT_OK 일 경우 Text에 받아온 주소 data를 띄워주게 합니다. data는 WebViewActivity에서 넘어오게 됩니다.

3. WebViewActivity.kt

class WebViewActivity : AppCompatActivity() {

    private lateinit var browser: WebView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_web_view)

        browser = findViewById(R.id.webView)
        browser.settings.javaScriptEnabled = true

        browser.addJavascriptInterface(MyJavaScriptInterface(), "Android")

        browser.setWebViewClient(object : WebViewClient() {
            override fun onPageFinished(view: WebView, url: String) {
//page loading을 끝냈을 때 호출되는 콜백 메서드
//안드로이드에서 자바스크립트 메서드 호출
                browser.loadUrl("javascript:sample2_execDaumPostcode();")
            }
        })
//최초로 웹뷰 로딩
        browser.loadUrl("")
    }

    inner class MyJavaScriptInterface {
        @JavascriptInterface
        fun processDATA(data: String?) {
            //자바 스크립트로 부터 다음 카카오 주소 검색 API 결과를 전달 받는다.
            val extra = Bundle()
            val intent = Intent()

            extra.putString("data", data)
            intent.putExtras(extra)
            setResult(RESULT_OK, intent)
            finish()
        }
    }
}

WebViewActivity.kt가 켜지면서 브라우저를 load 하게 됩니다. ( browser?.loadUrl("서버에 html 파일이 올라간 주소"))

browser?.loadUrl("javascript:sample2_execDaumPostcode();")
가 위의 html의 자바스크립트 코드가 됩니다. 서울과 같은 지명을 입력하면 이제 관련 지역을 리스트로 나오게 하는 함수가 되겠습니다.

그리고 리스트에 출력된 목록을 클릭 하게 되면, 데이터가 파싱되면서 fun processDATA(data: String?)를 콜백하면서 데이터가 전송되게 됩니다.

        btn_search.setOnClickListener {
            val intent = Intent(this@MainActivity, WebViewActivity::class.java)
            getSearchResult.launch(intent)
        }
        
        private val getSearchResult =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult())
        { results ->
            if(results.resultCode == RESULT_OK) {
                if(results.data != null) {
                    val data = results.data!!.getStringExtra("data")
                    et_address?.setText(data)
                }
            }
        }

getSearchResult가 registerForActivityResult 함수에 따라 WebViewActivity data를 받아서 텍스트에 띄우게 됩니다.

2. 클라이언트에서 설정(공식 문서 참고)


1. MainActivity.kt (init())

    private fun init() {

//        webViewAddress = // 메인 웹뷰
//        webViewLayout = // 웹뷰가 속한 레이아웃
// 공통 설정
        binding.webViewAddress.settings.run {
            javaScriptEnabled = true// javaScript 허용으로 메인 페이지 띄움
            javaScriptCanOpenWindowsAutomatically = true//javaScript window.open 허용
            setSupportMultipleWindows(true)
        }

        binding.webViewAddress.addJavascriptInterface(AndroidBridge(), "TestApp")
        binding.webViewAddress.loadUrl("")
        binding.webViewAddress.webChromeClient = webChromeClient

    }

2. MainActivity.kt (webChromeClient)

    private val webChromeClient = object: WebChromeClient() {

        /// ---------- 팝업 열기 ----------
        /// - 카카오 JavaScript SDK의 로그인 기능은 popup을 이용합니다.
        /// - window.open() 호출 시 별도 팝업 webview가 생성되어야 합니다.
        ///
        lateinit var dialog : Dialog

        @RequiresApi(Build.VERSION_CODES.O)
        override fun onCreateWindow(view: WebView, isDialog: Boolean,
                                    isUserGesture: Boolean, resultMsg: Message): Boolean {
            // 웹뷰 만들기
            var childWebView = WebView(view.context)
            Log.d("TAG", "웹뷰 만들기")
            // 부모 웹뷰와 동일하게 웹뷰 설정
            childWebView.run {
                settings.run {
                    javaScriptEnabled = true
                    javaScriptCanOpenWindowsAutomatically = true
                    setSupportMultipleWindows(true)
                }
                layoutParams = view.layoutParams
                webViewClient = view.webViewClient
                webChromeClient = view.webChromeClient
            }

            dialog = Dialog(this@MainActivity).apply {
                setContentView(childWebView)
                window!!.attributes.width = ViewGroup.LayoutParams.MATCH_PARENT
                window!!.attributes.height = ViewGroup.LayoutParams.MATCH_PARENT
                show()
            }

            // TODO: 화면 추가 이외에 onBackPressed() 와 같이
            //       사용자의 내비게이션 액션 처리를 위해
            //       별도 웹뷰 관리를 권장함
            //   ex) childWebViewList.add(childWebView)

            // 웹뷰 간 연동
            val transport = resultMsg.obj as WebView.WebViewTransport
            transport.webView = childWebView
            resultMsg.sendToTarget()

            return true
        }

        override fun onCloseWindow(window: WebView) {
            super.onCloseWindow(window)
            Log.d("로그 ", "onCloseWindow")
            dialog.dismiss()
            // 화면에서 제거하기
            // TODO: 화면 제거 이외에 onBackPressed() 와 같이
            //       사용자의 내비게이션 액션 처리를 위해
            //       별도 웹뷰 array 관리를 권장함
            //   ex) childWebViewList.remove(childWebView)
        }
    }

1. onCreateWindow

public boolean onCreateWindow (WebView view, 
                boolean isDialog, 
                boolean isUserGesture, 
                Message resultMsg)

호스트 응용 프로그램에 새 창을 만들도록 요청합니다. 호스트 응용 프로그램이 이 요청을 수락하도록 선택하면 이 메서드에서 true를 반환하고 창을 호스팅할 새 WebView를 만들고 이를 View 시스템에 삽입하고 새 WebView를 인수로 사용하여 제공된 resultMsg 메시지를 대상에 보내야 합니다.

호스트 응용 프로그램이 요청을 수락하지 않기로 선택하면 이 메서드에서 false를 반환해야 합니다. 이 메서드의 기본 구현은 아무 것도 하지 않으므로 false를 반환합니다.

resultMsg

Message : 새로운 WebView가 생성되면 보낼 메시지입니다. resultMsg.obj는 WebView.WebViewTransport 개체입니다. WebView.WebViewTransport.setWebView(WebView)를 호출하여 새 WebView를 전송하는 데 사용해야 합니다.

public Object obj

받는 사람에게 보낼 임의의 개체입니다. Messenger를 사용하여 프로세스 간에 메시지를 보낼 때 이것은 프레임워크 클래스의 Parcelable을 포함하는 경우에만 null이 아닐 수 있습니다(응용 프로그램에 의해 구현된 것이 아님). 다른 데이터 전송의 경우 setData(Bundle)를 사용하십시오.

여기에서 Parcelable 개체는 Build.VERSION_CODES.FROYO 릴리스 이전에는 지원되지 않습니다.

public void sendToTarget ()

이 메시지를 getTarget()에 의해 지정된 핸들러에 보냅니다. 이 필드가 설정되지 않은 경우 null 포인터 예외를 throw합니다.

3. reference

https://developers.kakao.com/docs/latest/ko/local/dev-guide#address-coord
https://postcode.map.daum.net/guide
https://developers.kakao.com/docs/latest/ko/getting-started/sdk-js
https://developer.android.com/guide/webapps/webview?hl=ko
https://developer.android.com/reference/android/webkit/WebChromeClient
https://developer.android.com/reference/android/os/Message#getTarget()
https://stuff.mit.edu/afs/sipb/project/android/docs/reference/android/webkit/WebChromeClient.html
http://www.dre.vanderbilt.edu/~schmidt/android/android-4.0/out/target/common/docs/doc-comment-check/reference/android/webkit/WebChromeClient.html

profile
가장 아름다운 정답은 서로의 협업안에 있다.

0개의 댓글