2026/03/05

오늘의 이야기

https://blog.protein.tech/document-your-kotlin-android-project-using-dokka-a4129a461203



 


Document Your Kotlin-Android Project Using Dokka


Keeping track of source code is challenging for any software project. It's even more challenging when you're working on an open-source…


blog.protein.tech




개발자로 살면서 제일 징그럽게 싫었던 것 중 하나, 문서화 


제일 필요하다고 생각했던 것도 문서화...


 


다양한 방법의 문서화 방법이 있을 것이라고 생각이 되기는 한다. 나의 경우는 코드 작업을 하면서 최대한 기억을 남겨 두기 위한 노력을 했었다.  그래도 잘 되지 않았던 것이 문서화를 하는 것이다.


 


구글링을 하다 발견한 문서 하나의 링크를 걸어 두었다. 두고두고 배워서 정리를 하리라 다짐하며...


 





오늘의 이야기


#스하리1000명프로젝트,
في بعض الأحيان يكون من الصعب التحدث مع العمال الأجانب، أليس كذلك؟
لقد صنعت تطبيقًا بسيطًا يساعد! أنت تكتب بلغتك، والآخرون يرون ذلك بلغتهم.
يترجم تلقائيًا بناءً على الإعدادات.
مفيد للغاية لإجراء محادثات سهلة. ألق نظرة عندما تحصل على فرصة!
https://play.google.com/store/apps/details?id=com.billcoreatech.multichat416




오늘의 이야기

앱의 기능 중에서 메시지 전송을 위하 기능을 구현 하고 있으나, 난관에 봉착했다. FCM 을 통해서 안드로이드 앱에서 message 전송을 하고 싶어서 매번의 릴리즈를 하고 있지만, 전송이 되지 않는 다. debug 상태에서는 잘 되는 데... 


 


왜 일까 ?


 


코드 난독화를 하기 위해서 설정을 한 것이 문제인 건가 ?  


 


내일은 알게 되기를 바라며... 오늘은 이만...


 


릴리즈에서 메시지가 전송이 안되는 로그



 


2022.06.25


이 문제를 테스트 하기 위해서 playstore 에 패치 등록을 5번 했다. ㅋ~


그래서 확인된 이슈는 코드 난독화 가 이슈의 발생지임을 알게 되었다.  그럼 이제 그것을 어떻게 해결할 것인가 ?


일단 정확하게 알지 못하는 현재는 코드 난독화를 하지 않는 것으로 해서 적용해 두었다.  다음에는 꼭 코드 난독화가 적용된 앱을 출시해 보아야 겠다.


 


buildTypes {
debug {
buildConfigField "Boolean", "DEBUG_MODE", "true"
resValue("string", "PORT_NUMBER", "8081")
minifyEnabled false
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
release {
buildConfigField "Boolean", "DEBUG_MODE", "false"
minifyEnabled false
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

현재 적용중인 gradle 파일의 일부분... minifyEnabled 을 다음에는 꼭 true 로 설정하게 될 길 바라며....





오늘의 이야기

오늘은 간략하게 나마, ArrayList 의 정렬 방법을 찾아 보았다.


 


chatMessageItem.content = respString + "\n( " + chatMessageItem.content + " )"
chatMessageItem.locale = sp.getString("languageCode", "kr").toString()
chatMesgItems.add(chatMessageItem)
if (isSort) {
chatMesgItems.sortBy {
it.seqNo
}
}

chatMessageItem 이라는 ArrayList 에 값을 넣는 순간 정렬이 필요한 경우가 생겼다. 그래서 간략하게 위 코드와 같이 정렬를 해 본다.


 


다만, chatMessageItems 는 Class 구조체를 가지고 있으므로 그 구조체에서 정렬할 키로 사용할 것을 잘 선택해야 한다. 


여기서 보는 seqNo 는 timesequence 을 이용한 값을 key로 사용하고 그것을 정렬하는 방식으로 사용해 보았다.


 


대화방 정렬하기 이미지



 


그림과 같이 대화방을 의 대화 내용이 시간순으로 정렬을 해 보고자 했을 때 사용해 볼 수 있었다.


 





오늘의 이야기


#billcorea #운동동아리관리앱
🏸 شنيدل، تطبيق ضروري لأندية كرة الريشة!
👉 مباراة اللعب - سجل النتائج وابحث عن المعارضين 🎉
مثالي لأي مكان، بمفردك، مع الأصدقاء، أو في النادي! 🤝
إذا كنت تحب كرة الريشة، جربها بالتأكيد

اذهب إلى التطبيق 👉 https://play.google.com/store/apps/details?id=com.billcorea.matchplay




오늘의 이야기

 


https://medium.com/firebase-tips-tricks/how-to-upload-an-image-to-cloud-storage-and-save-the-url-in-firestore-42711ca1df46



 


How to upload an image to Cloud Storage and save the URL in Firestore?


A simple solution for saving an image to Firebase Storage, writing the URL in Firestore, and reading it back using Jetpack Compose.


medium.com




 


요새 작업중인 앱에 적용해 볼 요량으로 이런 자료를 찾고 있었는데, 마침 찾았다. 잊을 까 싶어 링크를 걸어두어 본다. 


이 글에서는 firebase 의 cloud storage 을 활용하는 예시가 나와 있기는 하나, 예전에 포스팅 했던 글에서 처럼 base64로 encoding 해서 realtime database 에 저장을 하는 것도 방법이지 않을 까 싶다.


 


아무튼 cloud storage 에 저장하는 방법도 익혀 두면 길이 사용할 날이 있을 듯 하니, 살펴 보도록 해야겠다.


 


오늘도 화이팅





오늘의 이야기

앱을 만들다 보니, 주소 검색을 해야 하는 경우가 생긴다.  구글에서 찾아보면 추천해주는 방법이 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




오늘의 이야기

https://blog.protein.tech/document-your-kotlin-android-project-using-dokka-a4129a461203   Document Your Kotlin-Android Project Using Dokka K...