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

오늘의 이야기



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













오늘의 이야기

#스하리1000명프로젝트 스치니들! 내가 만든 이 앱은, 내 폰에 오는 알림 중에서 중요한 키워드가 있는 경우 등록해둔 친구에게 자동으로 전달해주는 앱이야 📲 예를 들어, 카드 결제 알림을 와이프나 자녀에게 보내주거나 이번 달 지출을 달력처럼...