위도 경도를 주소 정보로 바꿔서 제공하기
implementation ("com.google.android.gms:play-services-maps:19.0.0")
LocationUtils에 주소지로 변환하는 함수 추가
package com.lululalal.locationapp
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.location.Address
import android.location.Geocoder
import android.os.Looper
import androidx.core.content.ContextCompat
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
import com.google.android.gms.location.Priority
import com.google.android.gms.maps.model.LatLng
import java.util.Locale
class LocationUtils(val context: Context) {
//FusedLocationProviderClient -> 위도 경도 찾을 수 있음
private val _fusedLocationClient : FusedLocationProviderClient
= LocationServices.getFusedLocationProviderClient(context)
//실제 위치를 탐색할 함수
@SuppressLint("MissingPermission")
fun requestLocationUpdate(viewModel: LocationViewModel) {
//Callback : 무언가를 요청한 후 해당 작업이 완료될 때까지 기다린 다음 반환해주는 것
//시간이 걸릴 것 같은 경우 이 콜백 써준다
val locationCallback = object : LocationCallback(){
//클래스에서 상속받아 오버라이드 하는 대신 객체를 직접 만들고 그 내에서 바로 오버라이드 가능
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
//locationResult.lastLocation : 제일 최근 위치 반환
locationResult.lastLocation?.let {
//let 함수 안에서 it이 정보를 보여하고 있음
val location = LocationData(latitude = it.latitude, longitude = it.longitude) //it함수 안에서 정보 추출
viewModel.updateLocation(location) //뷰 모델에 위도경도 업데이트
}
}
}
//위치 요청 빌더 만들기
//LocationRequest.Builder(위치 정확도, 얼마나 자주 탐색할것인지).build()
//Priority엔 4가지 속성 있음
//정확도가 높을 수록 배터리 많이 사용
//1.PRIORITY_HIGH_ACCURACY : 높은 정확도
//2.PRIORITY_LOW_POWER : 배터리 덜 사용
//3.PRIORITY_BALANCED_POWER_ACCURACY : 배터리와 정확도 균형
//4.PRIORITY_PASSIVE : 배터리 아주 적게 사용
val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 1000).build()
//_fusedLocationClient의 requestLocationUpdates 매소드 사용하여 모든것을 하나로 묶기
_fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
//-> 해당 기능을 사용하고 싶을 떄마다 권한을 요청해야됨
//하지만 실제로 권한이 있을 떄만 이 함수를 실행할것이기떄문에 퍼미션을 무시하라는 어노테이션 붙여주기 : @SuppressLint("MissingPermission")
//Looper.getMainLooper() : 위치 업데이트를 위한 threading과 메세지 처리하는 데 사용함
//특정 looper를 제공해 어떤 스레드 위치 업데이트가 전달 될 지 정할 수 있음 지금은 메인 루퍼 전달함
}
//위치 권한에 액세스가 있는지 없는지 확인하는 함수
fun hasLocationPermission(context: Context):Boolean {
return ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED //PERMISSION_GRANTED문자열이 ACCESS_FINE_LOCATION에 있는지 체크하는 부분
&&
ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
}
//checkSelfPermission는 리턴 값이 int임 우린 Boolean이 필요 떄문에 PERMISSION_GRANTED와 같은 지 체크하여 액세스 승인했는지 체크함
fun reverseGeoDecodeLocation(location: LocationData) : String {
val geocoder = Geocoder(context, Locale.getDefault())
//Geocoder(Context, 언어 설정 (Locale.getDefault()는 폰의 기본 언어 설정 부름))
val coordinator = LatLng(location.latitude,location.longitude)
//구글 맵에서 제공하는 LatLng매소드에 좌표 설정
val addresses : MutableList<Address>? =
geocoder.getFromLocation(
coordinator.latitude,
coordinator.longitude,
1)
//특정 위치에 맞는 주소가 여러개 있을 수 있기 때문에 MutableList로 받음
//geocoder.getFromLocation(위도,경도,찾은 것중 리턴할 주소 갯수)
return if (addresses?.isNotEmpty() == true) {
addresses[0].getAddressLine(0)
} else {
"찾은 주소가 없습니다"
}
}
}
val address = location?.let {
locationUtils.reverseGeoDecodeLocation(location)
}
package com.lululalal.locationapp
import android.Manifest
import android.content.Context
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.core.app.ActivityCompat
import androidx.lifecycle.viewmodel.compose.viewModel
import com.lululalal.locationapp.ui.theme.LocationAppTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val viewModel:LocationViewModel = viewModel()
LocationAppTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
MyApp(viewModel)
}
}
}
}
}
@Composable
fun MyApp(viewModel: LocationViewModel) {//부모 컴프저블 만들기
val context = LocalContext.current //지금 내가 있는 액티비티의 context 요청
val locationUtils = LocationUtils(context)
LocationDisplay(locationUtils = locationUtils, viewModel,context = context)
}
@Composable
fun LocationDisplay(
locationUtils: LocationUtils,
viewModel: LocationViewModel,
context: Context
) {
//[1]권한 받기
//1. 권한 런처 표시
// rememberLauncherForActivityResult 사용
// 결과를 위해 액티비티를 시작 요청을 등록하는 것, 보이지 않는 곳에서 자동으로 관리 요청 코드 및 변환과 관련된 레코드가 레지스트리에 생성됨
//2. 권한이 왜 필요한지 알려줘야함
// rationaleRequired의 shouldShowRequestPermissionRationale() 메소드를 사용하여 왜 이 퍼미션이 필요한지에 대한 이유를 표시한다.
//[2]위치 데이터 얻기
//1.구글에서 제공하는 위도 경도 정보 얻어주는 것 그래이들에 추가
//2.위도 경도 데이터 클래스와 voewmodel작성
//3.LocationUtils에 위도 경도 받아오는 함수 작성
//4.display할 곳에서 위도 경도 지켜보는 뷰모델 매개변수로 받기 및 퍼미션 허용 했을 때 3번에서 작성한 함수 실행 하도록 호출
//5.위도 경도 화면에 표시해주기 위해 viewModel에 있는 자료형이 State인 location의 value값 가져오기
val location = viewModel.location.value
val address = location?.let {
locationUtils.reverseGeoDecodeLocation(location)
}
val requestPermissionLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestMultiplePermissions(), //contract는 permissions를 리턴함
onResult = {permissions ->
if (permissions[Manifest.permission.ACCESS_COARSE_LOCATION]==true
&& permissions[Manifest.permission.ACCESS_FINE_LOCATION]==true ) {
//위치에 권한 있다
//위도 -경도 셋팅하는 함수 호출!!
locationUtils.requestLocationUpdate(viewModel)
} else {
//권한 요청 필요
val rationaleRequired = ActivityCompat.shouldShowRequestPermissionRationale(
context as MainActivity, //메인 액티비치 말곤 다른창에선 이 팝업 열지말라는 등록
Manifest.permission.ACCESS_FINE_LOCATION
) || ActivityCompat.shouldShowRequestPermissionRationale(
context as MainActivity,
Manifest.permission.ACCESS_COARSE_LOCATION
)
//이 퍼미션이 필요한 이유를 설명해주기
if (rationaleRequired) {
Toast.makeText(context, "이 기능을 사용하려면 위치 권한이 필요합니다.", Toast.LENGTH_SHORT).show()
} else {
//rationaleRequired이 거부 상황일경우 사용자의 설정이나 안드로이드 폰 설정으로 이동
Toast.makeText(context, "설정에 들어가 위치권한을 활성화 해주세요.", Toast.LENGTH_SHORT).show()
}
}
}) //contract에서 리턴받은 permissions가 모두 허용됐는지 체크하는 부분
Column(modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
if (location != null) {
Text(text = "Address = ${location.longitude} : ${location.latitude} \n 이 곳은? $address")
} else {
Text(text = "Location not available")
}
Button(onClick = {
if (locationUtils.hasLocationPermission(context)) {
//이미 권한이 있으면 엡데이트
} else {
//위치 권한 요청
//여기서 실제로 포미션 요청을 런칭함!
requestPermissionLauncher.launch(
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
)
)
}
}) {
Text(text = "get location")
}
}
}