11_위치앱1(위치퍼미션&권한받기/4대 컴포넌트)

소정·2024년 7월 26일
0

Android_with_compose

목록 보기
11/17

1. 위치와 인터넷 퍼미션

위치를 찾는 방법은 여러가지인데 그중 ACCESS_COARSE_LOCATION와 ACCESS_FINE_LOCATION 사용

위치 퍼미션
ACCESS_COARSE_LOCATION : 대략적 위치 - 내 폰이 연결되어 있는 통신탑이나 지금 연결되어 있는 와이파이로 위치 찾음
ACCESS_FINE_LOCATION : 정확한 위치 - GPS나 위성 기반 위치 확인 방법 사용 꽤 높은 정확도로 기기 위치 탐색, 몇 미터 안팍, ACCESS_FINE_LOCATION는 단독으로 사용하지 못함

manifest에 퍼미션 쓰기

    <uses-permission android:name="android.permission.INTERNET"/>
    <!-- 대략적 위치 - 내 폰이 연결되어 있는 통신탑이나 지금 연결되어 있는 와이파이로 위치 찾음 -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <!-- 정확한 위치 - GPS나 위성 기반 위치 확인 방법 사용 꽤 높은 정확도로 기기 위치 탐색 -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

2. 권한 받기

헬퍼 클래스를 만들어 권한 받기, 매번 퍼미션 코드를 작성하기 않기 위해 하나의 파일로 만들어 관리
권한 승인이 됐는지 안됐는지 확인하는 용도

checkSelfPermission를 이용해서 사용자의 허용을 받아야하는 퍼미션을 체크한다
해당 퍼미션과 PackageManager.PERMISSION_GRANTED를 비교해서 PERMISSION_GRANTED문자열이 해당 퍼미션에 있는지 확인한다

package com.lululalal.locationapp

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import androidx.core.content.ContextCompat

class LocationUtils(val context: Context) {

    //위치 권한에 액세스가 있는지 없는지 확인하는 함수
//    fun hasLocationPermission(context: Context):Boolean {
//        if (ContextCompat.checkSelfPermission(
//                context,
//                Manifest.permission.ACCESS_FINE_LOCATION)== PackageManager.PERMISSION_GRANTED) {
//            return true
//        } else {
//            return false
//        }
//    }

    //쓸데없는 if문 삭제하고 리턴에 바로 쓰기
    fun hasLocationPermission(context: Context):Boolean {
        return ContextCompat.checkSelfPermission(
            context,
            Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED //PERMISSION_GRANTED문자열이 ACCESS_FINE_LOCATION에 있는지 체크하는 부분
    }
    //checkSelfPermission는 리턴 값이 int임 우린 Boolean이 필요 떄문에 PERMISSION_GRANTED와 같은 지 체크하여 액세스 승인했는지 체크함

}

4대 컴포넌트

1. context : 하나의 앱을 대표하며 앱에서 동작되는 모든 업무를 관여함

  • base context : 리소스 접근, 액티비티 시작 같은 기본 유틸리티 서비스를 제공함
  • activity context: 원하는 작업을 받아오는 곳

2. service : 자체 컨텍스트를 자기고 있음 백그라운드 작업 담당

  • 포그라운드 서비스 : 사용자에게 잘 보이는 작업. 포그라운드 서비스의 경우, 반드시 알림을 표시해야 하며, 사용자가 앱과 사용작용하지 않을 때도 계속 실행됨.
  • 백그라운드 서비스 : 사용자에게 직접 보이지 않는 작업. 네트워크(Network)와 연동이 가능
  • UI스레드(메인 스레드)가 담당한다.

3. Broadcast Receiver : 멀리서 에너지 신호를 포착하고 그것을 처리하여 정보를 전달하는 컴포넌트

  • OS로부터 발생하는 각종이벤트와 정보를 받아 핸들링함
  • 시스템 부팅시 앱 초기화, 네트워크 끊김같은 특수한 상황에대한 처리 그리고 배터리 부족 알림, 문자수신같은 정보를 받아서 하는 처리

4. Content Provider : 데이터를 관리하고 다른 Application의 데이터를 제공하는데 사용되는 컴포넌트

  • 애플리케이션 간의 데이터 공유를 위해 표준화된 인터페이스를 제공
  • 나 자신이 공개하고 공유하고 싶은 데이터만 공유할 수 있도록 도와준다
  • 음악 또는 사진 파일 등과 같이 용량이 큰 데이터들을 공유하는데 적합하다
  • 프로바이더는 데이터의 Read, Write에 대한 퍼미션이 있어야 Application에 접근이 가능하다

3. 권한 런처 표시하기

1. 권한 런처 표시

rememberLauncherForActivityResult를 사용하여 권한을 받는 팝업을 띄운다.
rememberLauncherForActivityResult엔 계약된 퍼미션과 결과값을 받아 처리한다

rememberLauncherForActivityResult 이란?
결과를 위해 액티비티를 시작 요청을 등록하는 것, 보이지 않는 곳에서 자동으로 관리 요청 코드 및 변환과 관련된 레코드가 레지스트리에 생성됨

package com.lululalal.locationapp

import android.Manifest
import android.content.Context
import android.os.Bundle
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.tooling.preview.Preview
import com.lululalal.locationapp.ui.theme.LocationAppTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            LocationAppTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                }
            }
        }
    }
}

//사용자의 위치를 얻고 업데이트하는 버튼을 포함하는 사용자 인터페이스용 function
@Composable
fun LocationDisplay(
    locationUtils: LocationUtils,
    context: Context
) {
    //권한 받기
    //1. 권한 런처 표시
    // rememberLauncherForActivityResult 사용
    // 결과를 위해 액티비티를 시작 요청을 등록하는 것, 보이지 않는 곳에서 자동으로 관리 요청 코드 및 변환과 관련된 레코드가 레지스트리에 생성됨

    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 ) {
                //위치에 권한 있다
            } else {
                //권한 요청 필요
            }
        }) //contract에서 리턴받은 permissions가 모두 허용됐는지 체크하는 부분, 위치 권한 요청의 결과가 포함되어 있음

    Column(modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(text = "Location not avaliable")
        
        Button(onClick = {
            if (locationUtils.hasLocationPermission(context)) {
                //이미 권한이 있으면 엡데이트
            } else {
                //위치 권한 요청

            }
        }) {
            Text(text = "get location")
        }
    }
}


2. 권한이 왜 필요한지 알려주기

rationaleRequired의 shouldShowRequestPermissionRationale() 메소드를 사용하여 왜 이 퍼미션이 필요한지에 대한 이유를 표시한다.

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 ) {
                //위치에 권한 있다
            } 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가 모두 허용됐는지 체크하는 부분

3. 퍼미션 요청을 런칭

위에서 작성한 것들을 버튼 클릭 시 런칭하도록 하기

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.compose.ui.tooling.preview.Preview
import androidx.core.app.ActivityCompat
import com.lululalal.locationapp.ui.theme.LocationAppTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            LocationAppTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                }
            }
        }
    }
}

@Composable
fun MyApp() {
    val context = LocalContext.current //지금 내가 있는 액티비티의 context 요청
}

@Composable
fun LocationDisplay(
    locationUtils: LocationUtils,
    context: Context
) {
    //권한 받기
    //1. 권한 런처 표시
    // rememberLauncherForActivityResult 사용
    // 결과를 위해 액티비티를 시작 요청을 등록하는 것, 보이지 않는 곳에서 자동으로 관리 요청 코드 및 변환과 관련된 레코드가 레지스트리에 생성됨
    //2. 권한이 왜 필요한지 알려줘야함
    // rationaleRequired의 shouldShowRequestPermissionRationale() 메소드를 사용하여 왜 이 퍼미션이 필요한지에 대한 이유를 표시한다.

    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 ) {
                //위치에 권한 있다
            } 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
    ) {
        Text(text = "Location not avaliable")
        
        Button(onClick = {
            if (locationUtils.hasLocationPermission(context)) {
                //이미 권한이 있으면 엡데이트
            } else {
                //위치 권한 요청

                //여기서 실제로 포미션 요청을 런칭함!
                requestPermissionLauncher.launch(
                    arrayOf(
                        Manifest.permission.ACCESS_FINE_LOCATION,
                        Manifest.permission.ACCESS_COARSE_LOCATION
                    )
                )
            }
        }) {
            Text(text = "get location")
        }
    }
}


4. 부모 컴프저블 만들기

LocationDisplay를 메인에 바로 사용하지않고 부모 컴프저블을 더서 호출함

@Composable
fun MyApp() {//부모 컴프저블 만들기
    val context = LocalContext.current //지금 내가 있는 액티비티의 context 요청
    val locationUtils = LocationUtils(context)
    LocationDisplay(locationUtils = locationUtils, context = context)
}

mainActivity.kt 총 코드

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.compose.ui.tooling.preview.Preview
import androidx.core.app.ActivityCompat
import com.lululalal.locationapp.ui.theme.LocationAppTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            LocationAppTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    MyApp()
                }
            }
        }
    }
}

@Composable
fun MyApp() {//부모 컴프저블 만들기
    val context = LocalContext.current //지금 내가 있는 액티비티의 context 요청
    val locationUtils = LocationUtils(context)
    LocationDisplay(locationUtils = locationUtils, context = context)
}

@Composable
fun LocationDisplay(
    locationUtils: LocationUtils,
    context: Context
) {
    //권한 받기
    //1. 권한 런처 표시
    // rememberLauncherForActivityResult 사용
    // 결과를 위해 액티비티를 시작 요청을 등록하는 것, 보이지 않는 곳에서 자동으로 관리 요청 코드 및 변환과 관련된 레코드가 레지스트리에 생성됨
    //2. 권한이 왜 필요한지 알려줘야함
    // rationaleRequired의 shouldShowRequestPermissionRationale() 메소드를 사용하여 왜 이 퍼미션이 필요한지에 대한 이유를 표시한다.

    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 ) {
                //위치에 권한 있다
            } 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
    ) {
        Text(text = "Location not avaliable")
        
        Button(onClick = {
            if (locationUtils.hasLocationPermission(context)) {
                //이미 권한이 있으면 엡데이트
            } else {
                //위치 권한 요청

                //여기서 실제로 포미션 요청을 런칭함!
                requestPermissionLauncher.launch(
                    arrayOf(
                        Manifest.permission.ACCESS_FINE_LOCATION,
                        Manifest.permission.ACCESS_COARSE_LOCATION
                    )
                )
            }
        }) {
            Text(text = "get location")
        }
    }
}


profile
보조기억장치

0개의 댓글