질문 정리

컴포즈 블루투스 권한 없을 때 처리

five2week 2024. 6. 27. 16:09

문제 상황

현재 블루투스 권한이 없을 때, 스캔을 시도하면 버튼을 눌러도 사용자에게 피드백이 없습니다.

 

요구 사항

해당 기능을 사용하려고 할 때, 권한 체크를 수행하고 권한이 없을 경우, 다음의 조치를 취해야 합니다.

  • 이전에 권한 요청을 했다면
    • 권한이 필요한 이유를 팝업으로 표시
    • 설정으로 이동할 수 있는 버튼 제공
  • 이번이 권한 요청이 처음이면
    • 바로 권한을 요청

 

블루투스 통신을 위해 필요한 절차

  1. 휴대폰 블루투스가 켜져 있는지 확인하기
  2. 앱에서 블루투스 관련 권한 받기
  3. 스캔하기
  4. 기기 연결하기

 

블루투스 상태 모델

앱에서 블루투스 상태관리를 위하여 다음의 모델을 사용했습니다.

  • DISABLED: 사용자에게 블루투스를 활성화할 것을 요청합니다.
  • PERMISSION_DENIED: 사용자가 필요한 권한을 부여하지 않았을 때, 설정으로 유도하여 권한을 요청합니다.
  • READY: 블루투스와 모든 권한이 준비되어 있어서 바로 장치 검색이나 연결을 시작할 수 있는 상태입니다.
  • SEARCHING: 장치 검색을 진행 중이며, 검색 중에는 이 상태를 유지합니다.
  • CONNECTED: 특정 장치에 연결이 완료되어 데이터 교환을 할 수 있는 상태입니다.
  • ERROR: 기대하지 않은 문제가 발생했을 때 이 상태로 전환하여 오류 처리 로직을 수행할 수 있습니다.

 

코드 설명

1. 휴대폰 블루투스 기능 활성화

휴대폰의 블루투스 기능이 켜져 있는지 확인합니다. 블루투스가 꺼져 있다면 사용자가 이를 활성화하도록 요청하는 창을 띄웁니다.

@Composable
fun SetupHomeStateEffects(
    bluetoothAdapter: BluetoothAdapter?,
    bluetoothEnableLauncher: ActivityResultLauncher<Intent>,
    permissionLauncher: ActivityResultLauncher<Array<String>>
) {
    val context = LocalContext.current

    LaunchedEffect(bluetoothAdapter) {
        // Bluetooth 활성화 요청
        bluetoothAdapter?.let {
            if (!it.isEnabled) {
                val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
                bluetoothEnableLauncher.launch(enableBtIntent)
            }
        }
    }

    LaunchedEffect(Unit) {
    // 블루투스 사용 권한
        handleBlePermission(permissionLauncher, context)
    }
}

 

2. 블루투스 설정 화면으로 이동

사용자가 블루투스를 활성화하지 않고 원래 화면으로 돌아왔을 경우, 블루투스 설정 화면으로 이동시킵니다.

@Composable
fun RememberBluetoothEnableLauncher(
    context: Context,
): ActivityResultLauncher<Intent> =
    rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        if (result.resultCode != Activity.RESULT_OK) {
            context.startActivity(Intent(Settings.ACTION_BLUETOOTH_SETTINGS))
        }
    }

 

3. 블루투스 관련 권한 받기

각 버전에 필요한 권한 리스트를 구성하여 요청합니다.

private fun buildPermissionsList(): MutableList<String> {
    val permissions = mutableListOf(
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION
    )
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        permissions.addAll(
            listOf(
                Manifest.permission.BLUETOOTH,
                Manifest.permission.BLUETOOTH_ADMIN
            )
        )
    }
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        permissions.addAll(
            listOf(
                Manifest.permission.BLUETOOTH_CONNECT,
                Manifest.permission.BLUETOOTH_SCAN
            )
        )
    }
    return permissions
}

private fun handleBlePermission(
    permissionLauncher: ActivityResultLauncher<Array<String>>,
    context: Context
) {
    // 버전에 따른 권한 리스트 생성
    val permissions = buildPermissionsList()
    permissionLauncher.launch(permissions.toTypedArray())
}

 

4. 설명 다이얼로그

사용자가 이전에 권한 요청을 거절한 적이 있다면, 권한이 필요한 이유를 설명하는 다이얼로그를 띄웁니다. 이를 통해 사용자가 권한의 필요성을 이해하고 설정으로 이동할 수 있도록 합니다.

fun handleBlePermissionDialog(
    permissionLauncher: ActivityResultLauncher<Array<String>>,
    homeViewModel: HomeViewModel,
    context: Context
) {
    if (ActivityCompat.shouldShowRequestPermissionRationale(
            context as Activity,
            Manifest.permission.ACCESS_COARSE_LOCATION
        )
    ) {
	// 권한을 요청에 대한 추가적인 설명이 필요한 경우
        homeViewModel.updateBlePermissionChange(true)
    } else {
	// 그냥 권한을 요청하는 경우
        handleBlePermission(permissionLauncher, context)
    }
}
@Composable
fun HandleBlePermissionDialog(homeViewModel: HomeViewModel) {
    val context = LocalContext.current
    if (homeViewModel.showBlePermissionDialog.value) {
        ConfirmationDialog(
            title = stringResource(R.string.bluetooth_permission_title),
            body = stringResource(R.string.bluetooth_permission_explanation),
            acceptBtnTitle = stringResource(R.string.change_now),
            declineBtnTitle = stringResource(R.string.change_later),
            onDismissRequest = { homeViewModel.updateBlePermissionChange(false) },
            onDeclineClick = {
                homeViewModel.updateBlePermissionChange(false)
            }
        ) {
            homeViewModel.updateBlePermissionChange(false)
            // 설명 확인 후 버튼 누르면, 설정 화면으로 이동
            navigateToSettings(context)
        }
    }
}

 

5. 사용 전 권한 확인하기

BLE 통신을 하는 경우, 권한 상태를 확인하고 없다면 요청합니다.

private fun handleDeviceScanClick(
    bluetoothAdapter: BluetoothAdapter?,
    homeViewModel: HomeViewModel,
    permissionLauncher: ActivityResultLauncher<Array<String>>,
    context: Context
) {
    if (bluetoothAdapter != null) {
        val blePermissionResult = homeViewModel.blePermissionResult.value
        if (bluetoothAdapter.isEnabled && blePermissionResult == BLESTATES.READY) {
	  // 블루투스 스캔
        } else if (blePermissionResult == BLESTATES.PERMISSION_DENIED) {
            HandleBlePermissionDialog(permissionLauncher, homeViewModel, context)
        }
    }
}

@Composable
fun RememberPermissionLauncher(
    homeViewModel: HomeViewModel
): ActivityResultLauncher<Array<String>> =
    rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
        val allPermissionsGranted = permissions.entries.all { it.value }
        homeViewModel.updateBlePermissionResult(
            if (allPermissionsGranted) BLESTATES.PERMISSION_GRANTED else BLESTATES.PERMISSION_DENIED
        )
    }

 

6. 홈 화면

위의 코드들을 사용하는 방법입니다.

@Composable
fun HomeScreenContent(
    navController: NavController,
    homeViewModel: HomeViewModel,
) {
    val context = LocalContext.current
    val homeState by homeViewModel.homeViewState.collectAsState()

    val bluetoothAdapter = RememberBluetoothAdapter(context)

    // Initialize and prepare all launchers
    // 블루투스 온오프 런처
    val bluetoothEnableLauncher = RememberBluetoothEnableLauncher(context)
    // 퍼미션 관련 런처
    val permissionLauncher = RememberPermissionLauncher(homeViewModel)

    // Reactive effects for state updates
    SetupHomeStateEffects(
        homeViewModel,
        bluetoothAdapter,
        bluetoothEnableLauncher,
        permissionLauncher
    )

    Column {
        HomeHeader(homeState)
        HomeContent(navController, homeViewModel, bluetoothAdapter, homeState, permissionLauncher)
    }

    HandleBlePermissionDialog(homeViewModel)
}

 

참고링크

Bluetooth permissions  |  Connectivity  |  Android Developers

 

블루투스 권한  |  Connectivity  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 블루투스 권한 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 앱에서 블루투스 기능을 사용하려면 여

developer.android.com

https://developer.android.com/develop/connectivity/bluetooth/ble/ble-overview

 

블루투스 저전력  |  Connectivity  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 블루투스 저전력 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Android는 중심 역할로 저전력 블루투

developer.android.com