Search code examples
androidkotlinandroid-jetpack-composeandroid-camera

Request camera permission if user does not allow in kotlin jetpack compose?


I want to enable the user to take a photo using the camera, actually, it is something that is done everywhere and is not very difficult, but I could not do some of it here. If the user gives permission, everything works fine, I can take pictures with the camera, but if the user does not allow it, the permission request popup does not appear. If the user does not let it, I want the popup to appear until they enable it. It's supposed to be like this anyway. am I wrong? My codes are like this

I follow this documentation in medium :

https://medium.com/@dheerubhadoria/capturing-images-from-camera-in-android-with-jetpack-compose-a-step-by-step-guide-64cd7f52e5de

TakePhotoScreen

@Composable
fun TakePhotoScreenRoute(
    navHostController: NavHostController,
    sharedViewModel: SharedViewModel,
    viewModel: TakePhotoViewModel = hiltViewModel()
) {

    val state by viewModel.state.collectAsState()

    TakePhotoScreen(
        navHostController = navHostController,
        sharedViewModel = sharedViewModel,
        createImageFile = viewModel::createImageFile,
        saveUserImgToDataStore = viewModel::saveUserImgToDataStore,
        requestCameraPermission = viewModel::requestCameraPermission,
        state = state
    )
}

@Composable
fun TakePhotoScreen(
    navHostController: NavHostController,
    sharedViewModel: SharedViewModel,
    createImageFile: (Context) -> File,
    saveUserImgToDataStore: (String) -> Unit,
    requestCameraPermission: (ActivityResultLauncher<String>) -> Unit,
    state: TakePhotoScreenState
) {

    val context = LocalContext.current
    val file = createImageFile(context)
    val uri = FileProvider.getUriForFile(
        Objects.requireNonNull(context),
        BuildConfig.APPLICATION_ID + ".provider", file
    )

    var capturedImageUri by remember {
        mutableStateOf<Uri>(Uri.EMPTY)
    }

    val cameraLauncher =
        rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) {
            capturedImageUri = uri
        }

    val permissionLauncher = rememberLauncherForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted ->
        if (isGranted) {
            cameraLauncher.launch(uri)
        } else {
            Toast.makeText(context, "Fotoğraf çekmek için Kamera iznine ihtiyaç var.", Toast.LENGTH_SHORT).show()
            //navHostController.navigate(DashBoardScreen.ProfileScreen.route)
        }
    }

    Column(
        Modifier
            .fillMaxSize()
            .padding(10.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {


        Button(
            onClick = {
                val permissionCheckResult =
                    ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
                if (permissionCheckResult == PackageManager.PERMISSION_GRANTED) {
                    cameraLauncher.launch(uri)
                } else {
                    // Request a permission
                    permissionLauncher.launch(Manifest.permission.CAMERA)
                }
            }) {
            Text(text = "Capture Image From Camera")
        }
    }

    var navigate by remember { mutableStateOf(true) }

    if (capturedImageUri.path?.isNotEmpty() == true && navigate) {

        saveUserImgToDataStore(capturedImageUri.toString())
        //sharedViewModel.onChangeUserImage(capturedImageUri.toString())
        navHostController.navigate(DashBoardScreen.ProfileScreen.route)
        navigate = false
    }
}

TakePhotoViewModel

@HiltViewModel
class TakePhotoViewModel @Inject constructor(
    private val dataStore: PreferenceDataStoreHelper
): ViewModel() {


    private val _state = MutableStateFlow(TakePhotoScreenState())
    val state: StateFlow<TakePhotoScreenState> = _state.asStateFlow()

    fun saveUserImgToDataStore(userAvatar: String) {
        viewModelScope.launch {
            dataStore.putPreference(PreferenceDataStoreConstants.USER_IMG,userAvatar)
        }
    }

    fun requestCameraPermission(permissionLauncher: ActivityResultLauncher<String>) {
        permissionLauncher.launch(Manifest.permission.CAMERA)
    }

    fun createImageFile(context: Context): File {

        val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
        val imageFileName = "JPEG_$timeStamp.jpg"
        val image = File(context.externalCacheDir, imageFileName)
        return image
    }
}

data class TakePhotoScreenState(
    val imgFile: File? = null
)

In TakePhotoScren, when a button is clicked, it asks for permission for the camera, I want to remove this button and when the user comes to this screen, the permission request popup will appear and the camera will come. I tried to do it like this

  ...

 val permissionCheckResult =
        ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
    if (permissionCheckResult == PackageManager.PERMISSION_GRANTED) {
        cameraLauncher.launch(uri)
    } else {
        // Request a permission
        permissionLauncher.launch(Manifest.permission.CAMERA)
    }
  /*   Button(
            onClick = {
                val permissionCheckResult =
                    ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
                if (permissionCheckResult == PackageManager.PERMISSION_GRANTED) {
                    cameraLauncher.launch(uri)
                } else {
                    // Request a permission
                    permissionLauncher.launch(Manifest.permission.CAMERA)
                }
            }) {
            Text(text = "Capture Image From Camera")
        } */

  ...

But I get this error

java.lang.IllegalStateException: Launcher has not been initialized


Solution

  • Your app should respect the user's decision to deny a permission. Starting in Android 11 (API level 30), if the user taps Deny for a specific permission more than once during your app's lifetime of installation on a device, the user doesn't see the system permissions dialog if your app requests that permission again.

    As described in: https://developer.android.com/training/permissions/requesting#handle-denial

    In this scenario you must either navigate the user to the app settings to change it or simply show them that they cannot use this feature.