2026/03/05

오늘의 이야기

앱을 만들다 보니, 주소 검색을 해야 하는 경우가 생긴다.  구글에서 찾아보면 추천해주는 방법이 2가지 정도로 압축 된다고 볼 수 있을 것 같다.
 
1. Daum 우편번호 서비스 
  장정 : API 가 필요하지 않다.  사용 제한도 없다.  
  단점 : 안드로이드 앱에서 직접 호출이 되지 않는다.
 
https://postcode.map.daum.net/guide#usage

Daum 우편번호 서비스

우편번호 검색과 도로명 주소 입력 기능을 너무 간단하게 적용할 수 있는 방법. Daum 우편번호 서비스를 이용해보세요. 어느 사이트에서나 무료로 제약없이 사용 가능하답니다.

postcode.map.daum.net

 
2. 주소 검색 서비스
    장점 : API 을 이용하여 사용한다.  java 코드를 활용할 수 있다.
    단점 : API 등록이 필요함.
https://business.juso.go.kr/addrlink/openApi/apiExprn.do?cPath=99MA

API 체험하기

business.juso.go.kr


사실은 잘 모르겠는데, 2번 방식으로 안드로이드에서 구현이 될까는 고민을 해 봐야 할 것 같다.  그래서 1번을 해 보기로 했는데, 
 
문제가 또 있다. java script 방식으로 처리를 해야 하기 때문에 실시간으로 활용할 수 있는 서버가 있어야 한다는 것이다. 검색을 통해서 얻은 결론은 호스팅 서비스를 사용하거나 하는 방법이 필요하다는 것인데,  저렴하게(?) 사용을 해 보려 하니, 방법이 좀 어렵다. 
 
그래서 blogger.com 에서 사용하는 블로그 페이지를 이용해서 만들어 보기로 했다.  먼저 blogger 페이지를 만들어 보았다.
이 페이지는 아래에서 기술하는 activity 에서 호출만 하면 주소 검색을 위한 팝업 페이지가 자동으로 열리게 된다.
https://billcoreatech.blogspot.com/2022/06/blog-post.html

안드로이드 앱 만들기 : 카카오(Daum) 주소 검색 API 호출 페이지 만들기.

이 페이지는 blogger 페이지를 이용해서 Kakao 우편번호 서비스를 활용하는 페이지 구현을 위한 페이지 입니다.  구성을 위해서 제일 먼저 blogger 의 수정을 시작 합니다. 수정할 때 HTML 보기를 선택

billcoreatech.blogspot.com

 
이제 구현을 해 보아야 겠다. 먼저 blogger 관리자 화면에서 새 페이지를 하나 만들고 html 편집 모드로 들어가 보겠다.
 

페이지 수정 html 모드 들어가기


그 다음은 html 편집기에서 아래와 같은 코드를 복사해 붙여 넣기를 하면 된다.  script 부분만 가져와 사용 하면 되기 떄문에 이 부분만 옮겨 넣으면 된다.

<div id="layer" style="-webkit-overflow-scrolling: touch; display: block; overflow: hidden; position: fixed; z-index: 1;"></div>

구성을 위해서 제일 먼저 blogger 의 수정을 시작 합니다.<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>


addr_daum.html

0.00MB



전체 코드는 위에 파일을 받아서 script 부분만 복사해서 붙여 넣으면 된다.  이제 안드로이드 앱에서 호출 하는 부분을 구현해 보아야 겠다.
 
먼저 internet 사용을 해야 하기 때문에 manifest 파일에 권한을 추가한다.

<uses-permission android:name="android.permission.INTERNET" />

 
다음은 호출하는 activity 을 구현해 보아야 겠다.  web page 을 호출해야 하기 때문에 layout 에 webview 을 넣고, 그 안에 위에서 작성한 페이지를 호출하면 된다.   이제 layout 을 구성해 보면 다음과 같다.
 

<?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"
android:layout_width="match_parent"
android:layout_height="match_parent">

<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

 
아무것도 배치하지 않고 webView 만 배치 했다. 이번에는 activity 코드를 만들어 보아야 겠다.


import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.webkit.JavascriptInterface
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity
import com.billcoreatech.multichat416.databinding.ActivityAddressApiBinding

class AddressApiActivity : AppCompatActivity() {
private var webView: WebView? = null
var TAG = "AddressApiActivity";
lateinit var activityAddressApiBinding : ActivityAddressApiBinding

inner class MyJavaScriptInterface {
@JavascriptInterface
fun processDATA(data: String?) {
// 주소검색창에서 주소를 선택하면 그 결과값이 data 에 들어오기 떄문에 그것을
// 받아서 내가 만드는 앱 페이지로 넘기면 끝.
val intent = Intent()
intent.putExtra("data", data)
setResult(RESULT_OK, intent)
finish()
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// 위에서 작성한 블로거 페이지의 url
val blogspot = "https://billcoreatech.blogspot.com/2022/06/blog-post.html"

activityAddressApiBinding = ActivityAddressApiBinding.inflate(layoutInflater)
setContentView(activityAddressApiBinding.root)
activityAddressApiBinding.webView!!.settings.javaScriptEnabled = true
activityAddressApiBinding.webView!!.addJavascriptInterface(MyJavaScriptInterface(), "Android")
activityAddressApiBinding.webView!!.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
// 위 웹페이지가 load가 끝나면 코드에서 작성했던 script 을 호출한다.
view.loadUrl("javascript:sample2_execDaumPostcode();")
}
}
// 위 블로거 페이지를 호출 한다.
activityAddressApiBinding.webView!!.loadUrl(blogspot)
}
}

이제 실행을 시켜 보아야 겠다.
 



012

주소검색 예시


 
이제 호출하는 구현은 되었으니,  나의 앱에 적용하는 것만 남았다.
 





오늘의 이야기


#스하리1000명프로젝트,
Nawala sa Korea? Kahit na hindi ka nagsasalita ng Korean, tinutulungan ka ng app na ito na madaling makalibot.
Sabihin lang ang iyong wika—ito ay nagsasalin, naghahanap, at nagpapakita ng mga resulta pabalik sa iyong wika.
Mahusay para sa mga manlalakbay! Sinusuportahan ang 10+ wika kabilang ang English, Japanese, Chinese, Vietnamese, at higit pa.
Subukan ito ngayon!
https://play.google.com/store/apps/details?id=com.billcoreatech.opdgang1127




2026/03/04

오늘의 이야기

이 페이지는 다음 주소 검색 페이지를 호출하는 테스트 페이지 입니다.


이 페이지에는 아무것도 없습니다.  숨어 있는 script 가 있습니다.


 


https://billcorea.tistory.com/215



 


안드로이드 앱 만들기 : 주소 검색을 위한 Kakao 우편번호 서비스 활용해 보기


앱을 만들다 보니, 주소 검색을 해야 하는 경우가 생긴다. 구글에서 찾아보면 추천해주는 방법이 2가지 정도로 압축 된다고 볼 수 있을 것 같다. 1. Daum 우편번호 서비스 장정 : API 가 필요하지 않


billcorea.tistory.com




posting 된 위 글에서 나와 있는 android 코드를 활용해서 이 페이지를 호출해 보면  카카오 우편번호 서비스 창이 호출 되는 것을 확인 할 수 있습니다.


 


 




 





오늘의 이야기

 







유달산 올랐다. 걸어서 ?   목포 해양 케이블카 북항 승강장에서 고하도 가는 왕복을 끊었다. 고하도 가는 길에 유달산에서 내릴 수 있다니 ?  일단은 고하도를 갔다. 그곳에서 해변을 낀 데크가 길게 뻗어 있기는 했지만,  난 유달산을 올라 보기로 했다.  바로 돌아오는 케이블카를 타고, 유달산에서 내렸다.  중턱에서 내려서 인지 일등봉에 올리는 데 그리 오래 걸리지 않았다.  쭈욱 올라 돌아보니 뻥 뚤린 바다가 이쁘다.

다시 내려와 길을 나섰다. 오후에는 낚시를 해서 한마리 잡아 보리라... 톱머리 해수육장 근처 선착장에서 낚시를 해 보기로 했다.  오늘도 어복은 없는 지 한 번의 입질이 없다.  오늘 여전히 시간을 낚았다. 다음엔 기회가 있을 까 ?





오늘의 이야기



#스치니1000프로젝트 #재미 #행운기원 #Compose #Firebase

🎯 야 너 토요일마다 로또 확인하냐?
나도 맨날 "혹시나~" 하면서 봤거든 ㅋㅋ

근데 이제는 그냥 안 해
AI한테 맡겼어 🤖✨

그것도 구글 Gemini로다가!

그래서 앱 하나 만들었지
👉 "로또 예상번호 by Gemini" 🎱

AI가 분석해서 번호 딱! 뽑아줌
그냥 보고 참고만 하면 됨

재미로 해도 좋고…
혹시 모르는 거잖아? 😏


https://play.google.com/store/apps/details?id=com.billcorea.gptlotto1127




오늘의 이야기

아무런 준비도 없이 그저 떠나고 싶다는 생각이 들었던 점심 무렵 길을 나섰다.


호남고속도로 (운전중에는 사진을 활영하지 맙시다. 액티브 크루즈의 도움이 없다면 불가능.)



 


 


가고 싶었던 섬... 신안을 향해 무작정 아무 생각도 없이 오래 걸리기는 했다. 3시가 넘은 시간이 되어서야 경우 도착할 수 있었으니 말이다. 예전에는 섬이었던 신안군도 였을 것인데, 언젠가 다리로 연결되었다는 기사를 보았던 적이 있다. 다리가 너무 길어서 바람이 불면 흔들린다는 기사까지.


천사대교 가기전에



 


길어봐야 다리가 얼마나 ?  그건 안 이기는 했다. 구간단속 60km 라 더더 길게 느껴지는 다리를 건너는 것이 새롭기도 했고...


천사대교를 지나며 (구간단속60km 구간이라 크루즈의 도움을 받아 찍음)


무한의 다리


무한의 다리 (할미섬까지 연결되는 다리)



무한의 다리라고는 하나, 결국은 돌아오게 되는 걸... 돌아올 것을 왜 가는 가? 라고 묻는다면, 뭐 그래도 바다 위를 걸어 볼 수 있으니까?


 


 


등대라던데 (해질 무렵에 가면 좋을 것 같기는 하나, 숲속이라)



 


천사 대교 석양에 걸리다.



 


다시 천사 대교를 건너 돌아와 석양이 비치는 곳에서 한 컷... 이렇게 첫날을 마무리해 본다.  하루 종일 미친 듯이 운전을 하고 오기는 했는데, 왜 그랬을까 싶기도 하고. 그냥 마음 한편을 추스를 시간이 필요해 보여서, 이제 한 달 한 달이 어떻게 나에게 다가설까 궁금하기도 하고...  성인이 되고, 결혼을 하고 나서 나 혼자 길을 나서본 건 이번이 처음이고, 마지막일 될까 싶다.  


 


지금은 어느 선착장에 차을 세우고 이 밤을 지새울 준비를 해 보고 있다. 사실은 밤하늘의 별들을 보고 싶었는 데,  이 선착장은 가로등 불이 환해서 그건 좀 어렵지 않을까 하는 생각이 든다. 


 


오늘도 잘 살았으니, 파이팅, 좋은 일만 올 거다.   





오늘의 이야기


#스하리1000명프로젝트

스치니들!
내가 만든 이 앱은, 내 폰에 오는 알림 중에서 중요한 키워드가 있는 경우
등록해둔 친구에게 자동으로 전달해주는 앱이야 📲

예를 들어, 카드 결제 알림을 와이프나 자녀에게 보내주거나
이번 달 지출을 달력처럼 확인할 수도 있어!

앱을 함께 쓰려면 친구도 설치 & 로그인해줘야 해.
그래야 친구 목록에서 서로 선택할 수 있으니까~
서로 써보고 불편한 점 있으면 알려줘 🙏

👉 https://play.google.com/store/apps/details?id=com.nari.notify2kakao





오늘의 이야기

앱을 만들다가 이미지 활용을 위한 방법을 찾아보는 기회가 생겼다. 며칠간의 고민 끝에 방법이 정리가 되어 간다.


먼저 카메라에서 이미지 가져오기는 다음 링크를 참고 했음을 밝힌다.


 


https://sungbin.land/jetpack-compose-%EA%B0%A4%EB%9F%AC%EB%A6%AC-%EC%B9%B4%EB%A9%94%EB%9D%BC-%EC%97%90%EC%84%9C-%EC%82%AC%EC%A7%84-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0-cf517eaca8bd



 


Jetpack Compose 갤러리/카메라 에서 사진 가져오기


rememberLauncherForActivityResult


sungbin.land




 


다만, 가이드에서 말하는 cameraX을 활용하고 싶지는 않고, 기본 카메라 앱을 호출해서 촬영하고 그 결과 이미지만 받아오는 형태로 만들어 보고 싶었다.  그래서 이런저런 내용을 찾아보다가 저 글을 찾게 된 것이다. 감사하게도


 


결과를 이용하는 방식은 아래 코드와 같다. 다름이 있다면, 갤러리에서 이미지를 가져오는 형식은 uri 가 결과가 오고, 카메라의 결과는 bitmap으로 온다는 차이만 있음 다름이다.


// 갤러리에서 사진 가져오기
val launcher = rememberLauncherForActivityResult(contract =
ActivityResultContracts.GetContent()) { uri: Uri? ->
imageUri = uri
}
// 카메라로 사진 찍어서 가져오기
val takePhotoFromCameraLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.TakePicturePreview()) { takenPhoto ->
if (takenPhoto != null) {
val baos = ByteArrayOutputStream()
takenPhoto.compress(
Bitmap.CompressFormat.PNG,
100,
baos
)
val b: ByteArray = baos.toByteArray()
val encoded: String = Base64.encodeToString(b, Base64.DEFAULT)
editor.putString("profileImage", encoded)
editor.commit()
imageTy = false
} else {
imageTy = false
Log.e("takenPhoto", "canceled ...")
}
}

그래서 앱의 기능 구조에 맞게 uri는 이미지로 변환해 사용할 것이고, bitmap 은 그대로 저장해서 (byte type으로 전환 후) 사용할 것이기 때문에 코드 구현은 마무리가 되었다.


 


각각의 호출은 아래 예시와 같이 한다.  앞에 버튼은 갤러리를 호출하는 부분으로 image의 종류는 다 허용할 것이라서 아래 코드와 같이 구현을 하였고,  사진을 가져오는 부분은 카메라 앱을 호출하는 방식으로 구현을 하였다.


Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.End
) {
IconButton(onClick = {
imageTy = true
launcher.launch("image/*")
}) {
Icon(
imageVector = Icons.Default.PhotoAlbum,
contentDescription = "Search Profile",
tint = fontColor
)
}
IconButton(onClick = {
imageTy = true
takePhotoFromCameraLauncher.launch()
}) {
Icon(
imageVector = Icons.Default.Camera,
contentDescription = "Take a Picture",
tint = fontColor
)
}
}

 


이제 그 구현된 모습을 보자면 아래 그림과 같이 구현이 되었다.  프로필 이미지를 받아올 때 방법으로 갤러리와 카메라 버튼을 클릭하는 가에 따라서 위에서 구현된 source 가 동작을 할 것이다.


카메라앱 호출과 갤러리 호출 예시



 


실행되는 모습은 앱에서 직접 확인하시길... 아직은 upgrade 전이라서 찾아볼 수 없지만,  조만간 기능 구현이 끝나면 앱의 patch 가 실행 예정이다.


 





오늘의 이야기

 




내 주변에 이런 길들이 있다는 걸 이제야 알게 된다. 정부청사가 가까이에 있어서 얻는 것인가 싶기도 하지만, 대전에 머물게 된지도 벌써 15~6년이 넘어가는 것 같은데, 많이 변하고 있는 것 같기는 하다.


 


아파트 단지를 끼고 있어서 멀리 나가지 않아도 되고, 나름 큼직한 나무 사잇길이라 여름 햇볕도 그다지 두럽지(?)않은 길이기도 하다.  점심을 먹고 나서 걸어 보는 것도 좋고, 저녁 퇴근 후 집을 나서는 것도 좋다. 


 


하루 한번이라도 열심히 걸어서 내 몸속의 지방을 불살라 보리라...





오늘의 이야기


#스하리1000명프로젝트,
Terkadang sulit untuk berbicara dengan pekerja asing, bukan?
Saya membuat aplikasi sederhana yang membantu! Anda menulis dalam bahasa Anda, dan orang lain melihatnya dalam bahasa mereka.
Ini menerjemahkan secara otomatis berdasarkan pengaturan.
Sangat berguna untuk obrolan mudah. Lihatlah ketika Anda mendapat kesempatan!
https://play.google.com/store/apps/details?id=com.billcoreatech.multichat416




오늘의 이야기

https://medium.com/codex/the-newer-way-to-make-network-calls-on-android-7162e2c37fe9







The newer way to make Network Calls on Android


We have used technologies such as Retrofit and Volley for far too long, and the industry has also adapted these very efficiently…


medium.com





예전에도 비슷한 한 내용을 퍼 왔던 거 같기는 한데,  다시금 보게 되어 이 글을 남겨 두고자 한다. 그래야 나중에 다시 한번 살펴보고 적용해 볼 수 있지 않을까 하는 생각이 들어서 이기도 하다.

비동기식 통신을 할 때는 여태까지 retrofit을 이용해서 하고는 있는데, 어떤 게 더 구현하기 쉬운가? 어떤 게 더 응답을 잘 받을 수 있는 가?

하는 궁금함이 밀려오는 건 어쩔 수 없는 가 보다. 이 부분도 나중에 구현을 해 보게 되면, 그 이야기를 남겨 보도록 해야겠다.





오늘의 이야기

#스치니1000프로젝트 #재미 #행운기원 #Compose #Firebase 🎯 야 너 토요일마다 로또 확인하냐? 나도 맨날 "혹시나~" 하면서 봤거든 ㅋㅋ 근데 이제는 그냥 안 해 AI한테 맡겼어 🤖✨ 그것도 구글 Gemini로다...