Today's

길을 나서지 않으면 그 길에서 만날 수 있는 사람을 만날 수 없다

모바일 앱(안드로이드)

외국인 관광객을 위한 앱 개발 : Jetpack Compose + Firebase + Google Maps 완벽 가이드

Billcorea 2025. 6. 13. 15:18
반응형

 

 

Jetpack Compose + Firebase + Google Maps 완벽 가이드

앱 화면 마커 처리 등등

 

이 글은 Jetpack Compose를 활용하여 Firebase Realtime Database 및 Google Maps, WebView를 사용하는 앱을 개발하면서 겪었던 문제들과 그 해결법을 정리한 개발 노트입니다. 

1. Firebase에서 반경 필터링

Firebase Realtime Database는 위치 기반 검색을 기본 제공하지 않기 때문에, 클라이언트에서 위도/경도를 기준으로 거리 계산을 해야 합니다.

▶ 거리 계산 함수 (Haversine Formula)

fun haversine(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double {
    val R = 6371000 // 지구 범위 (m)
    val dLat = Math.toRadians(lat2 - lat1)
    val dLon = Math.toRadians(lon2 - lon1)
    val a = sin(dLat / 2).pow(2.0) + cos(Math.toRadians(lat1)) *
            cos(Math.toRadians(lat2)) * sin(dLon / 2).pow(2.0)
    val c = 2 * atan2(sqrt(a), sqrt(1 - a))
    return R * c
}
가상 검색 포인트: Firebase가 주변 범위 데이터를 검색할 때가 없으므로, 자동차를 추가가 되면 GeoFire 사용을 골고해 보세요.

▶ Firebase 데이터 가져오기 (매칭과 거리 판단)

val database = FirebaseDatabase.getInstance().getReference("places")
database.addListenerForSingleValueEvent(object : ValueEventListener {
    override fun onDataChange(snapshot: DataSnapshot) {
        val filtered = snapshot.children.mapNotNull { it.getValue(PlaceInfo::class.java) }
            .filter { place ->
                val dist = haversine(myLat, myLng, place.y, place.x)
                dist <= 5000 // 5km 범위
            }
        // filtered 데이터 참조
    }
    override fun onCancelled(error: DatabaseError) {}
})
주의: Firebase가 y 필드를 문자로 인식하면, 범위 조회가 복잡해진다. 동일 필드를 Double 형식으로 저장해야 합니다.

▶ Firebase index 건설

{
  "rules": {
    "places": {
      ".indexOn": ["y"]
    }
  }
}

이것은 하위 도적으로 전체 데이터가 다운로드되는 문제가 들지 않도록 해줍니다.

2. WebView 역할과 방식

Compose 에서 WebView 모드로 글을 보여주려면 AndroidView 를 이용합니다.

AndroidView(
    factory = { context ->
        WebView(context).apply {
            settings.javaScriptEnabled = true
            webViewClient = WebViewClient()
            loadUrl("https://example.com")
        }
    },
    modifier = Modifier.fillMaxSize()
)

▶ Accept-Language 지정 (도칭어)

webViewClient = object : WebViewClient() {
    override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
        val newHeaders = request.requestHeaders.toMutableMap()
        newHeaders["Accept-Language"] = "ja-JP" // 일본어 표시
        return super.shouldInterceptRequest(view, request)
    }
}

3. Google Maps Marker 통합

Compose 게임에서 Google Maps 마커의 색상을 변경하기 위핵 BitmapDescriptorFactory 를 사용합니다.

Marker(
    state = MarkerState(position = LatLng(lat, lng)),
    title = "Place Name",
    icon = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN)
)

▶ 캐스트 이미지로 적용

val bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.my_icon)
val descriptor = BitmapDescriptorFactory.fromBitmap(bitmap)

4. URL 공유과 외부 브라우저 연결

val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://example.com"))
context.startActivity(intent)

5. Material Icons 이용 도달

바로가기 아이콘은 Icons.Default.Public 또는 Icons.Default.Language 같은 것이 자세화된 이미지를 제공합니다.

후기

Jetpack Compose + Firebase + Google Maps 게임을 개발하면서 반드시 가지게 되는 문제와 관련된 것을 정리해보였습니다. 계속 개선되는 노후가 있으면 추가적으로 개입할 계획입니다.

반응형