2026/04/27

오늘의 이야기


#스하리1000명프로젝트

오늘 내가 만든앱 하나 알려주고 싶어, 이 앱은 알림수집기 라고 이름을 붙였는 데,
내 폰에 표시 되는 알림을 읽어서 내가 지정한 단어가 들어 있고, 지출기록을 남겨야 하는 알림이
있으면 수집하고, 카카오톡으로 친구에게 전달해 주는 기능을 구현해 줄꺼야. 📲

이번 패치에서는 하루 한번 지정한 시간에 나에게 알림(노티) 하도록 기능을 추가 했어. 🙏
한번 써보고 불편한 거 있으면 말해줘.

앱 바로가기
👉 https://play.google.com/store/apps/details?id=com.nari.notify2kakao





오늘의 이야기

 


📍 Android에서 Kakao 로컬 API로 주소/좌표 변환하기


주소검색



 


이 글에서는 안드로이드 앱에서 Kakao REST API를 사용하여 좌표 → 주소 변환, 주소 → 좌표 변환을 구현하는 방법을 설명합니다.
초보 개발자도 쉽게 따라 할 수 있도록 Retrofit2 + Coroutine + ViewModel 기반으로 단계별로 안내합니다.




✅ 1. 사전 준비


1.1. Kakao REST API 키 발급



  • 카카오 개발자 사이트에 로그인

  • 애플리케이션 등록 → 앱 키 → REST API 키 복사

  • Android 플랫폼 등록 → 패키지명 + 키 해시 입력 (중요!)


1.2. Android 권한 설정


AndroidManifest.xml에 다음 권한을 추가하세요.


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

위치 권한은 런타임 요청이 필요합니다 (Activity나 Composable에서).


---


✅ 2. Gradle 설정


build.gradle (Module) 파일에 다음을 추가하세요.


dependencies {
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.12.0"
}

---


✅ 3. Retrofit 구성


3.1. 데이터 클래스


data class KakaoAddressResponse(
val documents: List<Document>,
val meta: Meta
)

data class Meta(
val is_end: Boolean,
val pageable_count: Int,
val total_count: Int
)

data class Document(
val address: Address,
val address_name: String,
val address_type: String,
val road_address: RoadAddress,
val x: String,
val y: String
)

data class Address(
val address_name: String,
val b_code: String,
val h_code: String,
val main_address_no: String,
val mountain_yn: String,
val region_1depth_name: String,
val region_2depth_name: String,
val region_3depth_h_name: String,
val region_3depth_name: String,
val sub_address_no: String,
val x: String,
val y: String
)

data class RoadAddress(
val address_name: String,
val building_name: String,
val main_building_no: String,
val region_1depth_name: String,
val region_2depth_name: String,
val region_3depth_name: String,
val road_name: String,
val sub_building_no: String,
val underground_yn: String,
val x: String,
val y: String,
val zone_no: String
)

3.2. Retrofit 인터페이스


interface KakaoApiService {
@GET("v2/local/geo/coord2address.json")
suspend fun getAddressFromCoords(
@Header("Authorization") authorization: String,
@Query("x") longitude: Double,
@Query("y") latitude: Double
): KakaoAddressResponse

@GET("v2/local/search/address.json")
suspend fun getCoordsFromAddress(
@Header("Authorization") authorization: String,
@Query("query") query: String
): KakaoAddressResponse
}

3.3. Retrofit 클라이언트


object KakaoApiClient {
private const val BASE_URL = "https://dapi.kakao.com/"

private val logging = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}

private val client = OkHttpClient.Builder()
.addInterceptor(logging)
.build()

val apiService: KakaoApiService by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(KakaoApiService::class.java)
}
}

---


✅ 4. ViewModel + Coroutine 통합


Coroutine을 사용하여 API를 호출하고, StateFlow로 결과를 UI에 전달합니다.


class LocationViewModel : ViewModel() {

private val _address = MutableStateFlow<String?>(null)
val address: StateFlow<String?> = _address

private val apiKey = "KakaoAK YOUR_REST_API_KEY" // <-- 실제 키로 교체하세요

fun fetchAddress(lat: Double, lng: Double) {
viewModelScope.launch {
try {
val response = KakaoApiClient.apiService.getAddressFromCoords(
authorization = apiKey,
longitude = lng,
latitude = lat
)
val addressName = response.documents.firstOrNull()?.address?.address_name
_address.value = addressName ?: "주소 없음"
} catch (e: Exception) {
_address.value = "에러 발생: ${e.localizedMessage}"
}
}
}
}

---


✅ 5. Jetpack Compose에서 주소 출력


@Composable
fun AddressScreen(viewModel: LocationViewModel = viewModel()) {
val address by viewModel.address.collectAsState()

Column(modifier = Modifier.padding(16.dp)) {
Text(text = "현재 주소: ${address ?: "불러오는 중..."}")

Spacer(modifier = Modifier.height(8.dp))

Button(onClick = {
viewModel.fetchAddress(37.5665, 126.9780) // 서울시청 예시
}) {
Text("주소 가져오기")
}
}
}

---


🔚 마무리 정리



  • ✔️ Retrofit + Coroutine을 통해 네트워크 처리를 안전하고 깔끔하게 구성

  • ✔️ ViewModel과 StateFlow를 통해 UI와 데이터 연결

  • ✔️ 권한과 REST API 키 보안을 고려해 실제 배포 환경에서는 백엔드 연동 또는 키 보호 필요


이제 Kakao Local API를 통해 GPS 좌표와 주소를 자유롭게 변환할 수 있습니다!


 


결과 이미지는 다음과 같이 설정이 가능 합니다. 


위치 설정 결과 지표에 표시 하기



 




📘 도움이 되었나요? 댓글이나 공유로 응원해 주세요 😊





오늘의 이야기

 



Oracle의 PERCENTILE_CONT와 APPROX_PERCENTILE 활용


백분위



 


Oracle에서 백분위수를 계산할 때 사용할 수 있는 주요 함수와 활용 방법에 대해 정리해 보겠습니다.


PERCENTILE_CONT 함수


`PERCENTILE_CONT` 함수는 연속적인 값을 기반으로 특정 백분위수를 계산하는 함수입니다. 예제는 다음과 같습니다:


SELECT PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY column_name) FROM table_name;

하지만 `PERCENTILE_CONT`는 **상수 값**만을 매개변수로 받을 수 있어 동적으로 값을 변경하기 어렵습니다.


동적으로 백분위 값 설정하기


`PERCENTILE_CONT`의 매개변수를 동적으로 설정하는 방법은 제한적이지만 다음과 같은 방법을 고려할 수 있습니다:



  1. PL/SQL을 활용한 동적 SQL 실행

  2. WITH 절을 이용한 사전 백분위값 계산

  3. APPROX_PERCENTILE을 활용하여 근사값 반환


예를 들어, `WITH` 절을 활용하여 여러 백분위수를 계산할 수 있습니다:


WITH PercentileData AS (
SELECT column_name,
PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY column_name) AS percentile_25,
PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY column_name) AS percentile_50,
PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY column_name) AS percentile_75
FROM table_name
)
SELECT * FROM PercentileData;

APPROX_PERCENTILE 활용


Oracle 12c 이상에서는 `APPROX_PERCENTILE`을 활용하여 대량 데이터에서 빠르게 근사 백분위수를 계산할 수 있습니다.


SELECT APPROX_PERCENTILE(column_name, 0.75) FROM table_name;

이 함수는 빠른 성능을 제공하지만 `PERCENTILE_CONT`보다 정확성이 조금 낮을 수 있습니다.


결론


`PERCENTILE_CONT`를 동적으로 활용하는 것은 어렵지만, `WITH` 절을 이용한 미리 계산된 값 활용, `EXECUTE IMMEDIATE`를 통한 PL/SQL 실행, `APPROX_PERCENTILE`을 통한 근사값 추출 등의 방법을 사용할 수 있습니다.





오늘의 이야기

외국인 관광객들은 한국에 와서 어떻게 여행 정보를 얻을까?


길찾기



 


외국인들은 한국에 도착한 이후, 모바일 중심의 실시간 정보 검색과 탐색을 통해 여행을 즐깁니다. 아래는 주요 정보 탐색 수단과 흐름입니다.


1. 지도 앱 (Google Maps, 네이버 지도, 카카오맵)



  • Google Maps: 영어권 사용자 (미국, 필리핀 등)

  • 네이버지도 / 카카오맵: 일본, 대만, 중국 관광객도 많이 설치

  • 주요 기능: 길찾기, 별점/리뷰, 맛집/카페 탐색


2. SNS 검색 (Instagram, TikTok, 小红书, YouTube)



  • 감성 장소 탐색에 최적

  • 해시태그 검색 예시:

    • #seoulcafe, #홍대맛집, #韓国旅行, #เที่ยวเกาหลี



  • 짧은 영상/사진 콘텐츠로 빠르게 탐색 가능


3. 여행자 커뮤니티 / 오픈채팅



  • Reddit (r/KoreaTravel)

  • 페이스북 그룹 (예: "Travel in Korea")

  • 카카오톡 오픈채팅: 여행자끼리 실시간 정보 공유


4. 여행 앱 / 리뷰 플랫폼



  • Klook, TripAdvisor, Airbnb Experience 등에서 투어 예약

  • 한국 로컬 플랫폼: 야놀자, 쿠팡 트래블 (일부 국가에서 사용)


5. 현장 안내판 + QR 코드



  • 명동, 인사동 등 주요 관광지에는 다국어 안내판 존재

  • QR 코드 스캔 시 다국어 웹 페이지로 연결됨




📱 실제 흐름 예시


일본인 관광객 A씨가 명동에 도착 후 카페를 찾는다면:



  1. 인스타그램에서 #明洞カフェ 검색

  2. 좋은 카페를 발견 → 위치 확인 (Google Map 또는 Naver Map)

  3. 리뷰 확인 및 도보 경로 탐색




🎯 관광객들의 공통된 니즈



  • 현재 위치에서 가까운지

  • 인증샷/후기가 충분한지

  • 외국인 친화적인지 (영어 가능, Wi-Fi 제공 등)

  • 줄이 긴지, 예약이 필요한지




💡 블로그 운영자/서비스 기획자를 위한 팁



  • 다국어 + 위치 기반 + 실시간성을 갖춘 콘텐츠 또는 서비스 제공

  • SNS 유입 → 지도/앱 탐색 → 행동 전환 구조 이해 필요


 


** 앱을 기획 하고 간단히 만들어 보기는 했지만,  어딘가에서 홍보를 하는 것은 어려운 일 입니다. 그래서 이렇게 라도 앱이 있다는 것을 알려 보고자 합니다. 


 


https://play.google.com/store/apps/details?id=com.billcoreatech.opdgang1127&pcampaignid=web_share



 


옵디강 (Welcome) - Google Play 앱


인터넷에 널린(?) 전국 맛집 정보 제공


play.google.com




 





오늘의 이야기


#스하리1000명프로젝트,
A veces es difícil hablar con trabajadores extranjeros, ¿verdad?
¡Hice una aplicación sencilla que ayuda! Escribes en tu idioma y los demás lo ven en el suyo.
Se traduce automáticamente según la configuración.
Súper útil para chatear fácilmente. ¡Echa un vistazo cuando tengas la oportunidad!
https://play.google.com/store/apps/details?id=com.billcoreatech.multichat416




오늘의 이야기



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

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

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

그것도 구글 Gemini로다가!

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

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

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


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




오늘의 이야기

Android에서 Hilt + Room + Firebase Realtime Database를 함께 사용하는 구조 설계


앱의 사용자 정보 저장 화면 예씨



 


이 글은 Android 앱에서 Hilt를 사용한 의존성 주입, Room으로 로컬 DB를 구성하고, Firebase Realtime Database로 클라우드와 데이터를 연동하는 구조를 설계하는 방법을 다룹니다. 예시 코드마다 구체적인 설명과 함께, 주의사항과 실무 팁도 포함되어 있습니다.




🧱 프로젝트 구조 개요


📁 app/
├── di/ // Hilt 모듈 정의
├── data/
│ ├── local/ // Room 관련 코드
│ ├── remote/ // Firebase 관련 코드
│ ├── repository/ // Repository 패턴 구현
│ └── mapper/ // Entity ↔ Domain 변환
├── domain/ // 앱 전반에서 쓰이는 공통 데이터 모델
├── ui/ // Compose UI 화면
├── viewmodel/ // ViewModel 정의
└── MainActivity.kt

💡 팁: 레이어를 분리함으로써 유지보수가 쉬워지고, 테스트도 용이해집니다. 특히 Firebase와 Room을 함께 쓸 때는 'source of truth'를 명확히 구분해야 합니다.




📦 1. 도메인 모델 Member


data class Member(
val name: String = "",
val tokenId: String = "",
val role: String = "member",
val status: String = "",
val nextMatchIn: Int = -1,
val opponent: String = "",
val lat: Double = 0.0,
val lon: Double = 0.0
)

설명: 이 모델은 Room이나 Firebase에 의존하지 않는, 순수한 앱 로직 전용 데이터 모델입니다. ViewModel이나 Repository, Firebase 직렬화에 모두 사용될 수 있습니다.


주의: Firebase Realtime Database는 직렬화 시 기본 생성자와 모든 속성의 기본값을 요구하므로, = "", = -1 등을 반드시 지정해 주어야 합니다.




🏠 2. Room Entity MemberEntity


@Entity(tableName = "members")
data class MemberEntity(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val name: String,
val tokenId: String,
val role: String,
val status: String,
val nextMatchIn: Int,
val opponent: String,
val lat: Double,
val lon: Double
)

설명: Room은 반드시 @Entity@PrimaryKey가 필요합니다. 여기서는 id를 자동 생성 키로 사용하고, tokenId는 일반 필드로 처리합니다.


주의: Firebase의 tokenId는 앱 재설치 등으로 변경될 수 있기 때문에, 고유 식별자로 사용하지 말고 별도로 auto-generated ID를 쓰는 게 안전합니다.




🔁 3. Mapper 함수


fun Member.toEntity(): MemberEntity = MemberEntity(
name, tokenId, role, status, nextMatchIn, opponent, lat, lon
)

fun MemberEntity.toDomain(): Member = Member(
name, tokenId, role, status, nextMatchIn, opponent, lat, lon
)

설명: Room Entity와 앱 전용 모델 간에 변환을 책임지는 함수입니다. 이 함수를 통해 구조가 다르거나 어노테이션 충돌 없이 안전하게 변환할 수 있습니다.


팁: 이 함수를 mapper 패키지에 따로 두면 여러 레이어에서 재사용 가능합니다.




🧾 4. DAO 정의


@Dao
interface MemberDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(member: MemberEntity)

@Query("SELECT * FROM members")
fun getAll(): Flow<List>

@Delete
suspend fun delete(member: MemberEntity)
}

설명: Room에서 SQL 없이 데이터를 조작할 수 있는 핵심 인터페이스입니다. Flow를 리턴하여 Compose와 함께 reactive하게 사용할 수 있습니다.


주의: insert와 delete는 suspend로 지정해야 코루틴 안에서 호출 가능합니다. 비동기 안전성을 확보하세요.




📚 5. Room Database


@Database(entities = [MemberEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun memberDao(): MemberDao
}

설명: Room에서 사용하는 DB 클래스입니다. DAO를 연결하고 전체 데이터베이스를 관리합니다.


주의: 데이터베이스 이름, 버전 변경 시에는 마이그레이션을 고려해야 합니다.




☁️ 6. FirebaseDataSource


class FirebaseDataSource @Inject constructor() {
private val db = FirebaseDatabase.getInstance().getReference("members")

fun saveMember(member: Member) {
db.child(member.tokenId).setValue(member)
}

fun getAllMembers(onComplete: (List<Member>) -> Unit) {
db.get().addOnSuccessListener {
val members = it.children.mapNotNull { snap ->
snap.getValue(Member::class.java)
}
onComplete(members)
}
}
}

설명: Firebase 연동을 담당하는 클래스입니다. 네트워크 I/O만 책임지며, UI나 DB 레이어와는 분리되어야 합니다.


주의: 콜백 기반이므로 suspend 함수로 감싸거나 Coroutine 내부에서 다루는 것이 안전합니다.




📦 7. Repository


class MemberRepository @Inject constructor(
private val dao: MemberDao,
private val firebase: FirebaseDataSource
) {
fun getLocalMembers(): Flow<List<Member>> =
dao.getAll().map { it.map { e -> e.toDomain() } }

suspend fun insert(member: Member) {
dao.insert(member.toEntity())
firebase.saveMember(member)
}

suspend fun syncFromFirebase() {
firebase.getAllMembers { members ->
CoroutineScope(Dispatchers.IO).launch {
members.forEach { dao.insert(it.toEntity()) }
}
}
}
}

설명: 로컬(RDB)과 원격(Firebase) 데이터를 모두 처리하는 Repository입니다. ViewModel이나 UI는 이 계층만 접근하도록 구성합니다.


주의: CoroutineScope를 직접 사용할 경우 lifecycle을 주의해야 하며, ViewModelScope 안에서 호출하는 것이 안전합니다.




🧠 8. ViewModel


@HiltViewModel
class MemberViewModel @Inject constructor(
private val repository: MemberRepository
) : ViewModel() {

val members = repository.getLocalMembers()
.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())

fun insert(member: Member) {
viewModelScope.launch {
repository.insert(member)
}
}

fun sync() {
viewModelScope.launch {
repository.syncFromFirebase()
}
}
}

설명: UI 로직을 담당하는 ViewModel입니다. UI와 Repository 사이에서 데이터를 연결하며, LifecycleScope를 활용해 안전하게 비동기 작업을 처리합니다.




🧩 9. Hilt 모듈


@Module
@InstallIn(SingletonComponent::class)
object AppModule {

@Provides
fun provideDatabase(@ApplicationContext context: Context): AppDatabase =
Room.databaseBuilder(context, AppDatabase::class.java, "app_db").build()

@Provides
fun provideMemberDao(db: AppDatabase): MemberDao = db.memberDao()
}

설명: Hilt로 Room과 DAO를 의존성 주입하기 위한 설정입니다. Hilt가 컴파일 타임에 의존성 그래프를 구성합니다.


주의: @Provides는 SingletonComponent 범위에서 관리하므로, 앱 전체에서 인스턴스를 공유합니다.




🖼️ 10. UI 예제 (Jetpack Compose)


@Composable
fun MemberScreen(viewModel: MemberViewModel = hiltViewModel()) {
val members by viewModel.members.collectAsState()

Column {
members.forEach {
Text("🙋‍♂️ ${it.name} (${it.role})")
}

Button(onClick = {
val member = Member(name = "홍길동", tokenId = UUID.randomUUID().toString())
viewModel.insert(member)
}) {
Text("멤버 추가")
}
}
}

설명: Compose 화면에서 ViewModel의 데이터를 관찰하고 버튼으로 멤버를 추가하는 예제입니다.


팁: Compose에서는 collectAsState()를 활용해 Flow나 StateFlow를 쉽게 관찰할 수 있습니다.




✅ 마무리


Hilt, Room, Firebase를 조합하면 강력한 구조로 안정적인 앱을 만들 수 있습니다. 다만 각 기술의 동작 원리와 데이터 흐름을 분리하는 설계가 매우 중요합니다.



  • 💡 Entity ↔ Domain ↔ DTO 구조를 명확히 나누자

  • ☁️ Firebase는 항상 변할 수 있다는 전제하에 다루자

  • 🧠 ViewModel과 Repository에서 Flow와 Coroutine을 적극 활용하자





오늘의 이야기


#스하리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




오늘의 이야기

 


 


💡 DevExpress dxChart & dxDataGrid 활용 가이드


열심히 일하는 중.



 


이 블로그에서는 dxChart 및 dxDataGrid를 사용하는 방법에 대해 설명합니다. 각 질문별로 요점을 강조하고 예제를 포함하여 쉽게 이해할 수 있도록 구성하였습니다.


📊 dxChart 관련 질문 & 해결 방법


✅ 하나의 차트에서 Bar와 Line을 동시에 표시할 수 있나요?

가능합니다! dxChart에서는 여러 개의 시리즈를 활용할 수 있습니다.



$("#chartContainer").dxChart({
dataSource: myData,
series: [
{ type: "bar", valueField: "barValue", name: "Bar Chart" },
{ type: "line", valueField: "lineValue", name: "Line Chart" }
]
});


✅ 서로 다른 값의 범위를 가진 데이터를 동시에 표현하려면?

각 시리즈를 서로 다른 축(valueAxis)에 바인딩하면 됩니다.



$("#chartContainer").dxChart({
dataSource: myData,
series: [
{ type: "bar", valueField: "largeValue", name: "Large Data", axis: "largeAxis" },
{ type: "line", valueField: "smallValue", name: "Small Data", axis: "smallAxis" }
],
valueAxis: [
{ name: "largeAxis", position: "left", min: 0, max: 100000 },
{ name: "smallAxis", position: "right", min: -100, max: 100 }
]
});


✅ 소수점 2자리까지 표시하려면?

label.format 속성을 설정하여 해결할 수 있습니다.



$("#chartContainer").dxChart({
dataSource: myData,
series: [
{
valueField: "sales",
name: "Sales",
label: {
visible: true,
format: {
type: "fixedPoint",
precision: 2
}
}
}
]
});


📋 dxDataGrid 관련 질문 & 해결 방법


✅ 셀 값이 NaN으로 표시되는 경우, 공백으로 변경하는 방법?

`cellTemplate` 또는 `calculateCellValue`를 활용하여 NaN을 공백으로 변환할 수 있습니다.



$("#gridContainer").dxDataGrid({
dataSource: myData,
columns: [
{
dataField: "price",
cellTemplate: function(container, options) {
let value = options.value;
container.text(isNaN(value) ? "" : value.toFixed(2));
}
}
]
});


✅ dxChart에서 X축 데이터의 표시 순서를 역정렬하려면?

`argumentAxis.inverted` 옵션을 활용하면 **X축 순서를 반대로 변경**할 수 있습니다.



$("#chartContainer").dxChart({
dataSource: myData,
argumentAxis: { inverted: true },
series: [{ valueField: "value", argumentField: "category", type: "bar" }]
});


🎯 마무리


이 블로그에서는 dxChart 및 dxDataGrid의 핵심 기능을 정리했습니다. 더 많은 활용 방법을 알고 싶다면 DevExpress 공식 문서를 참고하세요!





2026/04/26

오늘의 이야기

 


1. 전체 기능 설계도(텍스트 플로우)


필요기능



[회원 관리]
|
v
[참여자 인식 (Watch)]
|
v
[팀 배정/리그 대진 생성]
|
v
[경기 진행]
| \
v \
[경기 결과 입력] [심박수 감지 및 알림 (Watch)]
| |
v |
[결과 저장 및 요약] <------/
|
v
[순위/통계/공유]



2. 주요 기능별 상세


A. 회원 관리



  • 회원 등록 및 목록 조회

  • 동호회 정보 관리


B. 참여자 인식 및 팀 배정



  • Wear OS Watch와의 연동

    • 워치 착용자 자동 인식

    • 인원 파악 및 명단 동기화



  • 팀 자동/수동 배정

    • 복식팀 조합 알고리즘

    • 임의/무작위 배정 선택




C. 리그 대진표 생성



  • 현재 팀 구성에 따른 리그 생성

  • 경기 일정표 및 매치업 자동 생성


D. 경기 진행



  • 경기 시작/종료 기록

  • 세트/포인트 입력(스마트폰/워치)

  • 실시간 경기 상황 표시

  • Watch에서 심박수 실시간 측정

    • 200bpm 이상 감지 시 알림

    • 알림에 따라 휴식 권고




E. 결과 처리/저장



  • 세트별 승패 기록

  • 2승 기준 자동 종료 판정/기록 저장

  • 각 팀/개인별 경기 통계


F. 리그 결과 및 공유



  • 순위 집계 및 시각화(리그/전체)

  • 통계(승률, 점수 등) 제공

  • 결과 내보내기(공유, 백업 등)




3. 모듈 간 관계 요약(도식 예시)



  • Watch 앱

    • [심박수 측정] → [알림 팝업]

    • [참여 인식] → [팀 선택] → [경기 결과 입력]

    • ↔ 데이터 실시간 동기화 (Firebase, RESTful API)



  • 스마트폰 앱

    • [회원 관리] → [참여 인원 동기화]

    • [팀/리그 설정] → [대진표 생성]

    • [경기 상황 모니터링] ← [Watch 결과 수신]

    • [리그 결과 및 통계] → [사용자 화면 표시/공유]






4. 기능 설계도 (Markdown 표 예시)























































주요기능 세부설명 연동방식 주요화면 예시
회원 관리 회원 등록, 수정, 목록 스마트폰 회원 목록/등록 화면
참여자 인식 Watch 착용자 자동 인식, 인원 동기화 Watch/스마트폰 참여자 리스트
팀 배정/리그 생성 팀 자동/수동 편성, 리그 대진 자동 생성 스마트폰 팀 구성/대진표 화면
경기 진행 세트/포인트 입력, 경기 진행 상태 표시 스마트폰/Watch 경기 진행/입력 화면
심박수 체크 및 알림 200bpm 이상 시 알림, 휴식 권고 Watch 알림(팝업)
결과 저장/통계 경기 결과 저장, 통계 집계 스마트폰/서버 결과, 통계 화면
리그 결과 공유 최종 순위, 통계 공유/내보내기 스마트폰 결과 요약/공유 화면

 





오늘의 이야기

🌟 Welcome Korea – 당신의 현지 음식 가이드 앱!


앱 섦영서



 


이전에 Welcome Jeju라는 앱을 아시나요? 제주도 내 맛집과 장소 추천을 모으는 데 집중했던 간단한 안드로이드 앱이었죠. 매일 "맛집" 태그가 달린 인기 블로그나 웹 게시물을 스크래핑하여 "핫플레이스" 정보를 수집했었어요.


이제 저희 앱이 더 넓은 목적을 가지고 진화했습니다!




왜 리뉴얼이 필요했을까요?


한국을 방문할 때 구글 지도는 특히 명동과 같은 지역에서 자세한 현지 정보가 부족한 경우가 많습니다. 네이버 지도나 카카오 맵 같은 앱들은 한국어 사용자에게 최적화되어 있지만, 외국인 관광객들에게는 불편한 경향이 있죠.


명동 근처에서 프로젝트를 진행하면서, 많은 외국인 관광객들이 휴대폰에 몰두한 채 어디로 가야 할지 헤매는 모습을 보았습니다. 저의 서툰 영어로는 큰 도움을 드릴 수 없었기에, 외국인 여행자들에게 더 나은 서비스를 제공하기 위해 앱을 업그레이드하기로 결심했습니다.


또한, 앱 경험을 개선하기 위해 다양한 AI 도구들에게 조언을 구하며 얻은 통찰력도 이번 업데이트에 반영했습니다.




업데이트된 버전의 새로운 기능은 무엇인가요?


새 버전은 환영 화면으로 시작하며, 이제 다음 언어들을 지원합니다:



  • 한국어

  • 영어

  • 중국어 (간체 & 번체)

  • 필리핀어

  • 베트남어

  • 태국어


저는 한국을 방문하는 관광객의 큰 부분을 차지하는 동남아시아 방문객들이 주로 사용하는 언어에 초점을 맞췄습니다.




🧭 주요 기능


1. 다국어 지원


인터페이스 언어는 시작할 때 선택할 수 있으며, 나중에 메뉴를 통해 변경할 수 있습니다.


2. 메인 화면 – 주변 탐색


다음 기준으로 식당이나 장소를 검색할 수 있습니다:



  • 키워드 (반경 5km 이내)

  • 카테고리

  • 한국 블로거들이 매일 선별한 데일리 핫플레이스


3. 음성 검색


키워드만 사용하여 음성으로 검색할 수 있습니다. 모국어로 말하면 앱이:



  • 음성 입력을 한국어로 변환하고,

  • 한국어로 검색한 다음,

  • 결과를 다시 사용자의 언어로 번역해줍니다.


🔐 참고: 이 기능을 사용하려면 음성 인식 권한이 필요합니다.


4. 검색 결과 아이콘



  • 📍 위치 마커: 검색 키워드에 기반한 위치

  • 🚩 깃발 마커: 매일 수집된 "핫플레이스" 강조 표시




📱 지금 다운로드하세요!


앱은 현재 플레이 스토어에서 다운로드할 수 있습니다:


👉 Welcome Korea (맛집 리스트 모아보기)


“웹에 흩어져 있는 모든 제주 음식 팁을 한곳에 모았습니다 – 이제 더 똑똑하고, 다국어를 지원하며, 관광객 친화적입니다.”


앱 다운로드 링크





오늘의 이야기

  🎾 Kotlin으로 복식 경기 Round-Robin 매칭 구성하기 라운드 로빈 구현해 보기   이 글은 Kotlin과 Jetpack Compose를 사용하는 Android 앱에서 복식 경기 매...