2026/02/19

오늘의 이야기


#스하리1000명프로젝트

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

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

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

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





오늘의 이야기

재외 제주인 : 제주도를 떠나 사는 제주사람.


나도 그중에 한 명... 떠난 지 하도 오래되어 이제는 제주도를 가도 내가 모르는 곳에 온 것 같고, 그래서 주변에 어딜 가보고 싶어도 잘 알 수 없는 현실이 되었다.  그래서 찾아보기로 했다. 주변에 가고 싶은 곳이 어디에 있는 가? 


 


현실은 제주도가 아닌 이곳에서 어떻게 그걸 다 찾을 것인가? ㅋㅋㅋ 그래도 다행(?)인 것은 인터넷을 서핑하다 보면 다양한 사람들이 방문한 다양한 방문 기록들이 차고 넘쳐난다는 것이다. 


 


https://billcoreapython.tistory.com/40



 


파이썬으로 제주의 맛집을 찾아서 (3)


오늘은 정리되고 있는 자료를 타인(?)들과 공유하기 위해서... Firebase 의 Realtime Database 에 저장을 해 보도록 하겠다. import requests import sqlite3 import firebase_admin from firebase_admin import c..


billcoreapython.tistory.com




옆집(?)에 포스팅 한 것처럼... 맛집을 찾아 기록해 주는 글들은 많다. 그것들은 어떻게 정리할 것인가?  그것이 고민인 것이지...


 


정리된 자료를 받아서 이제 하나의 로컬 데이터를 구성해 보도록 하겠다.  저 글에서는 수집된 정보가 firebase 의 realtime database에 기록되어 있으니 그것을 받아서 내가 보고 싶은 자료로 만들어 보는 것이다. 


 


먼저 기록된 데이터의 구조를 볼까 ?


제주맛집 정보 샘플



이런 자료들이 여러 개 수집되어 있고, 매일처럼 갱신된 자료를 수집하고 있다. 이것들을 어떻게 활용할 것인지? 생각을 해 보자.


 


먼저 카테고리를 분해(?) 해서 구분을 조회 기준을 만든다.   음식점 > 한식 > 해물, 생선... 이런 식으로 들어 있으니 split('>')을 하게 되면 값이 나누어질 것이고, 이것을 기록해 두기로 했다. 


 


    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());

jejuFD6 = FirebaseDatabase.getInstance().getReference("jejuFD6info");

jejuFD6.orderByKey().addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
int iCnt = 1 ;
dbHandler = DBHandler.open(MainActivity.this);
for(DataSnapshot ds : snapshot.getChildren()) {
JejuFD6infoBean dataBean = ds.getValue(JejuFD6infoBean.class);
Log.e(TAG, iCnt + ")=" + dataBean.getPlace_name());
Log.e(TAG, dataBean.getCategory_name());
/*
* 카테고리를 분해해서 코드정보를 만들기
*/
String[] className = dataBean.getCategory_name().replaceAll(" ", "").split(">");
long nCnt = 0 ;
Log.e(TAG, "length=" + className.length);
if (className.length > 1) {
nCnt = dbHandler.appendClassCode("A", className[1]);
Log.e(TAG, " A class=" + nCnt);
}
if (className.length > 2) {
nCnt = dbHandler.appendClassCode("B", className[2]);
Log.e(TAG, " B class=" + nCnt);
}
if (className.length > 3) {
nCnt = dbHandler.appendClassCode("C", className[3]);
Log.e(TAG, " C class=" + nCnt);
}
Log.e(TAG, dataBean.getY() + "/" + dataBean.getX());
nCnt = dbHandler.updateCnt(dataBean.getY(), dataBean.getX(), dataBean.getPlace_name()) ;
Log.e(TAG, " cntData =" + nCnt);
try {
Log.e(TAG, dataBean.getUrl());
} catch (Exception e) {

}
nCnt = dbHandler.insert(dataBean);
Log.e(TAG, " infoData =" + nCnt);
iCnt++;
}
dbHandler.close();
}

@Override
public void onCancelled(@NonNull DatabaseError error) {

}
}) ;
}

코드를 보면 split(">") 하기 전에 공백을 제거하는 처리를 먼저 했다.  데이터를 받아서 보니 글자들 사이에 들어가 있는 공백은 별 필요가 없는데, 그리고 나누어 기록을 했다.   그걸 하기 위해서 먼저 데이터를 저장할 dbHelper을 구현해 보았다.


    /**
* aClassCode, bClassCode, cClassCode : 코드 분류 1,2,3 차
* cntData : 위치별-상호별 중복 건수
* infoData : 상세 정보 y,x,place_name 으로 cntData 와 연계
* @param db
*/
@Override
public void onCreate(SQLiteDatabase db) {

StringBuffer sb = new StringBuffer();
sb.append("create table aClassCode");
sb.append("( _id integer primary key autoincrement, ");
sb.append(" className text ");
sb.append(" ) ");
db.execSQL(sb.toString());

sb = new StringBuffer();
sb.append("create table bClassCode");
sb.append("( _id integer primary key autoincrement, ");
sb.append(" className text ");
sb.append(" ) ");
db.execSQL(sb.toString());

sb = new StringBuffer();
sb.append("create table cClassCode");
sb.append("( _id integer primary key autoincrement, ");
sb.append(" className text ");
sb.append(" ) ");
db.execSQL(sb.toString());

sb = new StringBuffer();
sb.append("create table cntData");
sb.append("( _id integer primary key autoincrement, ");
sb.append(" y text, ");
sb.append(" x text, ");
sb.append(" place_name text, ");
sb.append(" atCnt number ");
sb.append(" ) ");
db.execSQL(sb.toString());

sb = new StringBuffer();
sb.append("create table infoData");
sb.append("( _id integer primary key autoincrement, ");
sb.append(" y text, ");
sb.append(" x text, ");
sb.append(" address_name text, ");
sb.append(" category_group_code text, ");
sb.append(" category_group_name text, ");
sb.append(" category_name text, ");
sb.append(" distance text, ");
sb.append(" id text, ");
sb.append(" phone text, ");
sb.append(" place_name text, ");
sb.append(" place_url text, ");
sb.append(" road_address_name text, ");
sb.append(" url text, ");
sb.append(" regTime text ");
sb.append(" ) ");
db.execSQL(sb.toString());

}

 


오늘의 코딩은 여기까지...  이제 기본 화면을 구성해 봐야 할 텐데...


 


카테고리 별 조회 샘플



 


오늘의 작업은 여기 까지... 카테고리를 선택하면 해당 카테고리별로 리스트가 나오는 화면을 만들었다.





오늘의 이야기

https://developer.android.com/guide/topics/connectivity/telecom/selfManaged?hl=ko 



 


통화 앱 빌드  |  Android 개발자  |  Android Developers


통화 앱 빌드 통화 앱을 통해 사용자는 기기에서 음성 통화 또는 영상 통화를 받거나 걸 수 있습니다. 다음 스크린샷과 같이 통화 앱은 통화 시 기본 전화 앱 인터페이스를 사용하는 대신 자체


developer.android.com




이런 건 무리일까?


 


전화 기본 앱을 만들어 본다는 것은... 개발자 문서를 봐서는 도저히 감당이 되지 않을 것 같은 생각이 들기는 하지만,


그런데, 저런 앱을 하나 만들었다쳐도 나 말고 누가 사용할 것인가?


누군가에게 애써 만든 걸 줄 수 있나? ㅋㅋㅋ


 





오늘의 이야기


#스하리1000명프로젝트,
有时候和外劳说话很难,对吧?
我制作了一个简单的应用程序,可以帮助您!你用你的语言写作,其他人用他们的语言看到它。
它根据设置自动翻译。
超级方便,可以轻松聊天。有机会就来看看吧!
https://play.google.com/store/apps/details?id=com.billcoreatech.multichat416




오늘의 이야기

아직 잘 모르겠다. 내 앱에서 알림이 왔을 때, 내 손목에 있는 워치에도 알림이 동시에 뜨고 하는 것들에 대해서, 이제 조금 이해가 되기 하기는 하지만, 아직 정리가 잘 되지 않는다.


 


https://codechacha.com/ko/notifications-in-android/



 


안드로이드의 다양한 Notification 종류와 구현 방법


Android의 다양한 Notification 종류와 구현 방법에 대해서 정리하였습니다. 먼저 Notification Channel을 등록해야 하고, Notification은 BigText, BigPicture, Inbox, Messaging, Media Style 등으로 구현할 수 있습니다. 또


codechacha.com




여러 가지 구현에 대한 이야기를 적어 놓고 있는데, 아직 적용을 해 보지 못해서 다 이해가 되는 것은 아니기는 하지만,


조금은 알 것 같기도 하고...


 


일단은 내가 만든 앱에서 알림이 뜨면 내 손목에 있는 워치에도 알림이 뜨기는 한다. 그리고 한 가지 꼭 기억하고 가야 할 것은 내 폰의 설정에서 알림 부분에 내가 만든 앱의 알림이 허용되어 있는지 봐야 한다. 또한 워치 앱에서도 (갤럭시 워치 4는 Galaxy Wearable)에서 알림 부분에 내 앱의 알림에 대한 설정이 허용되어 있는지 봐야 한다.


난 그것도 이해를 하지 못해서 몇 날(?)을 허송세월을 보낸 것 같다.


 


ㅋ... 


 


여하튼 저 알림을 다 이해하는 날이 올때까지.... 파이팅~





오늘의 이야기

안드로이드 개발자 문서에서는 아래 링크와 같이 예시를 볼 수 있다.  오늘은 그중에서 그냥 쉽게 EditBox 에서 입력한 내용을 클립보드에 저장해서 이용하는 예제를 만들어 보겠다. 


 


https://developer.android.com/guide/topics/text/copy-paste?hl=ko#java 



 


복사하여 붙여넣기  |  Android 개발자  |  Android Developers


복사하여 붙여넣기 Android는 복사하여 붙여넣기를 지원하는 강력한 클립보드 기반 프레임워크를 제공합니다. 텍스트 문자열, 복잡한 데이터 구조, 텍스트 및 바이너리 스트림 데이터, 애플리케


developer.android.com




 


먼저 내가 만드는 앱에서 이용할 부분을 찾아보자.  




자주 사용할 수 있는 부분이 Editbox 가 될 것 같다.


예을 들어 오른쪽 그림과 같은 예시 화면이 있다면


 


제목 에 입력된 내용을 클립보드에 넣었다가


내용에 붙여넣는 것을 해 보기로 하겠다.


 


 


 


 


 


 


 


 


 


import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Bundle;

...

/**
* A simple {@link Fragment} subclass.
* Use the {@link BoardAppendFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class BoardAppendFragment extends Fragment implements OnBackPressedListener {

...

ClipboardManager clipboard ;
ClipData clipData ;

public BoardAppendFragment() {
// Required empty public constructor
}

...

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...

clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);

...

binding.edTitle.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
clipData = ClipData.newPlainText("title", binding.edTitle.getText().toString());
clipboard.setPrimaryClip(clipData);
Toast.makeText(getContext(), getString(R.string.msgClipboardCutText), Toast.LENGTH_SHORT).show();
return true;
}
});
return binding.getRoot();
}

...
}

내용은 간단하다... Clipboard 와 Clipdata 을 선언하고 clipbaord 을 구현했다.  그리고 화면에 있는 editbox 의 longclick 이벤트을 이용해서 editbox 에 입력된 텍스트를 클릭보드에 담아내는 것 까지만 구현을 했다. 


 


붙여넣기는 붙여넣을 곳에 가서 마찬가지로 long click 을 하면 붙여넣기 메뉴가 안드로이드에서 표시를 해 주니 그것을 이용하면 된다 


 





실행되는 모습은 위 동영상과 같이 제목에 글자를 입력하고 long click 을 해서 클립보드에 붙여진 것을 확인후 


내용에 가서 long click 으로 붙여 넣기를 해 보는 것이다.   


 


 


 





오늘의 이야기


#billcorea #운동동아리관리앱
🏸 Schneedle,羽毛球俱乐部必备应用!
👉 比洞赛 – 记录分数并寻找对手 🎉
适合任何地方,独自一人、与朋友一起或在俱乐部! 🤝
如果你喜欢羽毛球,一定要尝试一下

前往应用程序👉 https://play.google.com/store/apps/details?id=com.billcorea.matchplay




오늘의 이야기

ListView 에서는 SetOnItemClick 이벤트의 활용이 넘나 쉬었다. 그냥 선언하고 구현만 하면 되었으니, 그런데, RecycleView 을 사용해서 구현하다 보니 이런일이 생긴다. RecycleView 에서는 클릭 이벤트 등을 구현하는게 힘들다. 그래도 어쩌라 구현은 해야 되고.


 


폭풍 구글링~~~ 그래서 찾아낸 것은 이런 것들이다. RecycleView 에서 Holder 을 이용해서 item 을 배치 하고 그 Holder 의 item 을 클릭하는 것을 이용 하도록 하는 것이다. 


 


public class ChatBotAdapter extends RecyclerView.Adapter<ChatBotAdapter.ViewHolder> {

private static final String TAG = "ChatAdapter";
private final ArrayList<ChatMsgModel> chatMsgModels;
String userEmail ;

public ChatBotAdapter(ArrayList<ChatMsgModel> items, String userEmail) {
this.chatMsgModels = items;
this.userEmail = userEmail;
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.custom_chat_msg, parent, false);
return new ViewHolder(view);
}

@Override
public void onBindViewHolder(final ViewHolder holder, int position) {

ChatMsgModel vo = chatMsgModels.get(position);

...

holder.userid_tv.setText(vo.getNickName()); // userId 대신 nickName 으로 대체
holder.date_tv.setText(vo.getCrt_dt());
holder.content_tv.setText(vo.getContent());
}

@Override
public int getItemCount() {
return chatMsgModels.size();
}

public class ViewHolder extends RecyclerView.ViewHolder {
public ConstraintLayout my_cl, other_cl;
public TextView userid_tv, date_tv, content_tv

public ViewHolder(View view) {
super(view);

userid_tv = view.findViewById(R.id.userid_tv);
date_tv = view.findViewById(R.id.date_tv);
content_tv = view.findViewById(R.id.content_tv);

...


// 2021.11.01 item 클릭 처리를 위해서 추가
itemView.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
int pos = getAdapterPosition() ;
if (pos != RecyclerView.NO_POSITION) {
// 리스너 객체의 메서드 호출.
if (mListener != null) {
mListener.onItemClick(v, pos) ;
}
}
}
});
}
}

// 2021.11.01 리스너 객체 참조를 저장하는 변수
private ChatBotAdapter.OnItemClickListener mListener = null ;

// OnItemClickListener 리스너 객체 참조를 어댑터에 전달하는 메서드
public void setOnItemClickListener(ChatBotAdapter.OnItemClickListener listener) {
this.mListener = listener ;
}

public interface OnItemClickListener {
void onItemClick(View v, int position) ;
}
}

이렇게 adapter class 에서 OnItemClickListener 을 구현하고 그것을 호출해서 사용하면 되게 되는 것이다. 


생각해 보니 이전에도 기술했던 내용인 것 같기는 한데... 뭐 어쩌랴... ㅋㅋㅋ 기억은 항상 되새김질을 해야 하는 것이고, 오랜만에 보게 되면 새로 보는 느낌이 든다. 


 


 


이제 구현된 것을 활용하는 소스를 구현해 보자.


     ...

ChatBotAdapter adapter ;

...

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityChatRoomBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());

...

adapter = new ChatAdapter(chatMsgModels, userEmail);
binding.rv.setLayoutManager(new LinearLayoutManager(ChatRoomActivity.this));
binding.rv.setAdapter(adapter);

...

adapter.setOnItemClickListener(new ChatAdapter.OnItemClickListener() {
@Override
public void onItemClick(View v, int position) {

...

}
});

Log.e(TAG, "onCreate -----------------------------------------") ;
}

이렇게 MainActivity 의 onCreate 에서 구현을 해 보면 ListView 에서는 해당 ListView.setOnClickListener 을 활용했다고 하면, RecycleView  에서는 RecycleView.Adapter 을 이용해서 adapter.setOnItemClickListener 을 활용하는 방법이 될 수 있다고 생각이 든다. 


 


그럼... 이번에도 즐~ 코딩 하시길...





오늘의 이야기

Recycleview 을 사용하다보니, RecycleAdapter 을 구성해서 화면을 구현 하게 된다.  당연한...


잠깐 소스를 볼까 ?


 


<잘못된 예시>


    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityChatRoomBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());

setSupportActionBar(binding.mytoolBar);

sp = getSharedPreferences(getPackageName(), MODE_PRIVATE);

chatMsgModels = new ArrayList<>();
chatRoom = FirebaseDatabase.getInstance().getReference(chatRoomKey);

.....

adapter = new ChatAdapter(chatMsgModels, userEmail);
binding.rv.setLayoutManager(new LinearLayoutManager(ChatRoomActivity.this));
binding.rv.setAdapter(adapter);

chatRoom.orderByChild("crt_dt").startAfter(now).addChildEventListener(new ChildEventListener() {
@Override
public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String previousChildName) {
// Database 의 정보를 ChatMsgVO 객체에 담음
ChatMsgModel chatMsgVO = dataSnapshot.getValue(ChatMsgModel.class);
Log.e(TAG, "crt_dt=" + chatMsgVO.getCrt_dt()) ;
chatMsgModels.add(chatMsgVO);

// 채팅 메시지 배열에 담고 RecyclerView 다시 그리기
adapter = new ChatAdapter(chatMsgModels, userEmail);
binding.rv.setAdapter(adapter);
binding.rv.scrollToPosition(chatMsgModels.size()-1);
Log.e(TAG, chatMsgModels.size()+"");
}

@Override
public void onChildChanged(@NonNull DataSnapshot snapshot, @Nullable String previousChildName) {

}

@Override
public void onChildRemoved(@NonNull DataSnapshot snapshot) {

}

@Override
public void onChildMoved(@NonNull DataSnapshot snapshot, @Nullable String previousChildName) {

}

@Override
public void onCancelled(@NonNull DatabaseError error) {

}
});

.....
}

흔히 하는 실수일 것 같다. adapter 을 구현해 사용하고자 하는 경우


adapter = new ChatAdapter(chatMsgModels, userEmail);  을 선언해 구현하는데,  간혹 데이터를 읽어오고 다시 넣어주는 시점에 다시 new 아덥터를 구현하는 경우 ... 위 예시와 같은 구성을 하는 경우에...


 


아래 부분 어딘가에서  adapter  을 이용한 이벤트를 실행하고자 하면, 동작을 하지 않는 것 같은 느낌이 올때가 있다. 왜 일까 ? 참 여러시간 고민을 했던 기억이 있어서 이렇게 글로 남겨 보는 건데.  new 아답터 해서 생성하는 것은 첨에 한번만 하는 것으로 해야지 위 에서와 같이 onChildAdded 내부에서 다시 new ChatAdapter 을 해서 생성하는 것은 새로 객체가 생성이 되었기 때문에 아래 부분에서 adapter.SetOnClickListener 등을 구현하게 되었을 때, 혼돈의 구렁텅이에 빠지게 될 것이다. 


 


<올바른 예시>


    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityChatRoomBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());

setSupportActionBar(binding.mytoolBar);

sp = getSharedPreferences(getPackageName(), MODE_PRIVATE);

chatMsgModels = new ArrayList<>();
chatRoom = FirebaseDatabase.getInstance().getReference(chatRoomKey);

.....

adapter = new ChatAdapter(chatMsgModels, userEmail);
binding.rv.setLayoutManager(new LinearLayoutManager(ChatRoomActivity.this));
binding.rv.setAdapter(adapter);

chatRoom.orderByChild("crt_dt").startAfter(now).addChildEventListener(new ChildEventListener() {
@Override
public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String previousChildName) {
// Database 의 정보를 ChatMsgVO 객체에 담음
ChatMsgModel chatMsgVO = dataSnapshot.getValue(ChatMsgModel.class);
Log.e(TAG, "crt_dt=" + chatMsgVO.getCrt_dt()) ;
chatMsgModels.add(chatMsgVO);

// 채팅 메시지 배열에 담고 RecyclerView 다시 그리기
binding.rv.setAdapter(adapter);
binding.rv.scrollToPosition(chatMsgModels.size()-1);
Log.e(TAG, chatMsgModels.size()+"");
}

@Override
public void onChildChanged(@NonNull DataSnapshot snapshot, @Nullable String previousChildName) {

}

@Override
public void onChildRemoved(@NonNull DataSnapshot snapshot) {

}

@Override
public void onChildMoved(@NonNull DataSnapshot snapshot, @Nullable String previousChildName) {

}

@Override
public void onCancelled(@NonNull DatabaseError error) {

}
});

.....
}

이렇게 구현을 해야 그런 실수를 범하지 않게 된다.


 


오늘도 즐~





오늘의 이야기


#스하리1000명프로젝트,
In Korea verloren? Auch wenn Sie kein Koreanisch sprechen, hilft Ihnen diese App dabei, sich problemlos fortzubewegen.
Sprechen Sie einfach Ihre Sprache – es übersetzt, sucht und zeigt Ergebnisse in Ihrer Sprache an.
Ideal für Reisende! Unterstützt mehr als 10 Sprachen, darunter Englisch, Japanisch, Chinesisch, Vietnamesisch und mehr.
Probieren Sie es jetzt aus!
https://play.google.com/store/apps/details?id=com.billcoreatech.opdgang1127




2026/02/18

오늘의 이야기



보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.













오늘의 이야기

조금 지나긴 했지만, 이슈가 되었던 요소수, 그걸 판매하는 주유소 정보를 공공데이터 포털에서 제공하기 시작했다.  현재 (2021.12.20 기준)는 111개 주유소의 정보만 제공이 되고 있는 것 같으나, 일단 그걸 이용해서 데이터 제공을 하는 앱을 ...