13_위치앱3(지오디코딩_위도 경도 주소로 변환)

소정·2024년 7월 26일
0

Android_with_compose

목록 보기
13/17

위도 경도를 주소 정보로 바꿔서 제공하기

1. 구글에서 제공하는 map 디펜던시

implementation ("com.google.android.gms:play-services-maps:19.0.0")

2. 위도&경도 -> 주소지로 변경

LocationUtils에 주소지로 변환하는 함수 추가

  1. Geocoder설정
    -> Geocoder 클래스는 위도와 경도를 주소로 변환하는 데 사용
  2. 1번에서 추가한 구글 맵에서 제공하는 매소드 중 LatLng 매소드 사용하여 좌표 설정
  3. getFromLocation 사용하여 주소지로 변경
    -> getFromLocation은 제공된 위도와 경도에 기반하여 Address 객체의 목록을 반환
    -> geocoder.getFromLocation(위도,경도,찾은 것중 리턴할 주소 갯수)
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 {
            "찾은 주소가 없습니다"
        }

    }

}

3. 화면에 표시하기

  • locationUtils에 만든 reverseGeoDecodeLocation함수를 LocationDisplay()함수 안에서 선언하기
  • 선언 시 let함수를 이용해 return값 바로 사용
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")
        }
    }
}


참고) https://tutorials.eu/integrating-location-services-in-your-android-app-day-11-android-14-masterclass/

profile
보조기억장치

0개의 댓글