admin管理员组

文章数量:1391969

I am trying to implement camera functionallity to my android app. I use jetpack compose and I saw they have updated cameraX library to support it. I followed these medium tutorials:
Part 1:
Part 2:
And at first it worked. Then I tried to add some overlay to the camera preview, like an info text to explain to the user what to do. And if I do:

uiState.surfaceRequest?.let { request ->
    CameraXViewfinder(
        surfaceRequest = request,
        modifier = modifier
            .fillMaxSize()
    )
    Row (
         verticalAlignment = Alignment.CenterVertically,
         modifier = Modifier
             .padding(
                end = 48.dp,
                bottom = dimensionResource(R.dimen.padding_medium)
              )
              .background(Color.LightGray.copy(alpha = 0.5f), MaterialTheme.shapes.small)
              .width(275.dp)
    ) {
       Text(
            text = "Scan a qr code or point at a place to see more details about it!",
            modifier = Modifier
                .padding(dimensionResource(R.dimen.padding_small))
       )
    }

It still works fine. But I decided that I want to add a button that when clicked will show the row and after 5 seconds it will disappear, like this:

@Composable
fun CameraScreen(
    modifier: Modifier = Modifier,
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
    cameraViewModel: CameraViewModel = CameraViewModel(),
) {
    val uiState = cameraViewModel.uiState.collectAsStateWithLifecycle().value
    val context = LocalContext.current
    LaunchedEffect(lifecycleOwner) {
        cameraViewModel.bindToCamera(context.applicationContext, lifecycleOwner)
    }

    uiState.surfaceRequest?.let { request ->
        CameraXViewfinder(
            surfaceRequest = request,
            modifier = modifier
                .fillMaxSize()
        )
        Box(
            contentAlignment = Alignment.BottomEnd,
            modifier = Modifier
                .fillMaxSize()
        ) {
            AnimatedVisibility(
                visible = uiState.isInfoScreenOpen
            ) {
                LaunchedEffect(Unit) {
                    delay(5000)
                    cameraViewModel.toggleInfoScreen()
                }
                Row (
                    verticalAlignment = Alignment.CenterVertically,
                    modifier = Modifier
                        .padding(
                            end = 48.dp,
                            bottom = dimensionResource(R.dimen.padding_medium)
                        )
                        .background(Color.LightGray.copy(alpha = 0.5f), MaterialTheme.shapes.small)
                        .width(275.dp)
                ) {
                    Text(
                        text = "Scan a qr code or point at a place to see more details about it!",
                        modifier = Modifier
                            .padding(dimensionResource(R.dimen.padding_small))
                    )
                }
            }
            IconButton(
                onClick = cameraViewModel::toggleInfoScreen
            ) {
                Icon(
                    painter = painterResource(R.drawable.round_info_outline_24),
                    contentDescription = "Info button",
                    tint = Color.White
                )
            }
        }
    }
}

It doesn't work anymore and when I press the button or the delay passes the screen disappears and I only see the background and I get the "SurfaceView BufferQueue has been abandoned" error in logcat.

My uiState data class:

data class CameraUiState (
    // Used to set up a link between the Camera and your UI.
    val surfaceRequest: SurfaceRequest? = null,
    val qrContent: Uri? = null,
    val isInfoScreenOpen: Boolean = true
)

My viewModel code:

private val _uiState = MutableStateFlow(CameraUiState())
val uiState: StateFlow<CameraUiState> = _uiState.asStateFlow()

private val executor = Executors.newSingleThreadExecutor()

private val cameraPreview = Preview.Builder().build().apply {
    setSurfaceProvider { newSurfaceRequest ->
        _uiState.update {
            it.copy(
                surfaceRequest = newSurfaceRequest
            )
        }
    }
}

fun toggleInfoScreen() {
        _uiState.update {
            it.copy(
                isInfoScreenOpen = !it.isInfoScreenOpen
            )
        }
    }

    suspend fun bindToCamera(appContext: Context, lifecycleOwner: LifecycleOwner) {
        val processCameraProvider = ProcessCameraProvider.awaitInstance(appContext)
        processCameraProvider.bindToLifecycle(
            lifecycleOwner, DEFAULT_BACK_CAMERA, cameraPreview, imageAnalysis
        )

        // Cancellation signals we're done with the camera
        try { awaitCancellation() } finally { processCameraProvider.unbindAll() }
    }

I tried to move surfaceRequest out of the uiState, so that it was collected separetly, so it doesn't depend on the other fields of the uiState. I tried to move the call for the creation of the viewModel in the NavHost and not directly into the composable, but this gives me the same error without even starting the camera. I tried moving out all the code except the CameraXViewfinder out of the let block, so that it doesn't depend on the existing of surfaceRequest and it renders before the camera, yes, but after I click the button the whole thing disappears, i.e. both the camera view and the row and button composables. I first tried implementing a scaffold as a container and the button was a floating action button, because I thought it would be easier. I tried wrapping the code shown above into another box, to simplify the code, and then I removed the whole wrapping container. I tried to copy as much as possible the code from the second part of the medium tutorial and I see no logical differences between that and my solution. I tried asking ai with no result. I don't know what else to do and I do not understand how to fix this, because I do not understand why exatcly it is happening.

I appreaciate all the help I can get.

I am trying to implement camera functionallity to my android app. I use jetpack compose and I saw they have updated cameraX library to support it. I followed these medium tutorials:
Part 1: https://medium/androiddevelopers/getting-started-with-camerax-in-jetpack-compose-781c722ca0c4
Part 2: https://medium/androiddevelopers/tap-to-focus-mastering-camerax-transformations-in-jetpack-compose-440853280a6e
And at first it worked. Then I tried to add some overlay to the camera preview, like an info text to explain to the user what to do. And if I do:

uiState.surfaceRequest?.let { request ->
    CameraXViewfinder(
        surfaceRequest = request,
        modifier = modifier
            .fillMaxSize()
    )
    Row (
         verticalAlignment = Alignment.CenterVertically,
         modifier = Modifier
             .padding(
                end = 48.dp,
                bottom = dimensionResource(R.dimen.padding_medium)
              )
              .background(Color.LightGray.copy(alpha = 0.5f), MaterialTheme.shapes.small)
              .width(275.dp)
    ) {
       Text(
            text = "Scan a qr code or point at a place to see more details about it!",
            modifier = Modifier
                .padding(dimensionResource(R.dimen.padding_small))
       )
    }

It still works fine. But I decided that I want to add a button that when clicked will show the row and after 5 seconds it will disappear, like this:

@Composable
fun CameraScreen(
    modifier: Modifier = Modifier,
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
    cameraViewModel: CameraViewModel = CameraViewModel(),
) {
    val uiState = cameraViewModel.uiState.collectAsStateWithLifecycle().value
    val context = LocalContext.current
    LaunchedEffect(lifecycleOwner) {
        cameraViewModel.bindToCamera(context.applicationContext, lifecycleOwner)
    }

    uiState.surfaceRequest?.let { request ->
        CameraXViewfinder(
            surfaceRequest = request,
            modifier = modifier
                .fillMaxSize()
        )
        Box(
            contentAlignment = Alignment.BottomEnd,
            modifier = Modifier
                .fillMaxSize()
        ) {
            AnimatedVisibility(
                visible = uiState.isInfoScreenOpen
            ) {
                LaunchedEffect(Unit) {
                    delay(5000)
                    cameraViewModel.toggleInfoScreen()
                }
                Row (
                    verticalAlignment = Alignment.CenterVertically,
                    modifier = Modifier
                        .padding(
                            end = 48.dp,
                            bottom = dimensionResource(R.dimen.padding_medium)
                        )
                        .background(Color.LightGray.copy(alpha = 0.5f), MaterialTheme.shapes.small)
                        .width(275.dp)
                ) {
                    Text(
                        text = "Scan a qr code or point at a place to see more details about it!",
                        modifier = Modifier
                            .padding(dimensionResource(R.dimen.padding_small))
                    )
                }
            }
            IconButton(
                onClick = cameraViewModel::toggleInfoScreen
            ) {
                Icon(
                    painter = painterResource(R.drawable.round_info_outline_24),
                    contentDescription = "Info button",
                    tint = Color.White
                )
            }
        }
    }
}

It doesn't work anymore and when I press the button or the delay passes the screen disappears and I only see the background and I get the "SurfaceView BufferQueue has been abandoned" error in logcat.

My uiState data class:

data class CameraUiState (
    // Used to set up a link between the Camera and your UI.
    val surfaceRequest: SurfaceRequest? = null,
    val qrContent: Uri? = null,
    val isInfoScreenOpen: Boolean = true
)

My viewModel code:

private val _uiState = MutableStateFlow(CameraUiState())
val uiState: StateFlow<CameraUiState> = _uiState.asStateFlow()

private val executor = Executors.newSingleThreadExecutor()

private val cameraPreview = Preview.Builder().build().apply {
    setSurfaceProvider { newSurfaceRequest ->
        _uiState.update {
            it.copy(
                surfaceRequest = newSurfaceRequest
            )
        }
    }
}

fun toggleInfoScreen() {
        _uiState.update {
            it.copy(
                isInfoScreenOpen = !it.isInfoScreenOpen
            )
        }
    }

    suspend fun bindToCamera(appContext: Context, lifecycleOwner: LifecycleOwner) {
        val processCameraProvider = ProcessCameraProvider.awaitInstance(appContext)
        processCameraProvider.bindToLifecycle(
            lifecycleOwner, DEFAULT_BACK_CAMERA, cameraPreview, imageAnalysis
        )

        // Cancellation signals we're done with the camera
        try { awaitCancellation() } finally { processCameraProvider.unbindAll() }
    }

I tried to move surfaceRequest out of the uiState, so that it was collected separetly, so it doesn't depend on the other fields of the uiState. I tried to move the call for the creation of the viewModel in the NavHost and not directly into the composable, but this gives me the same error without even starting the camera. I tried moving out all the code except the CameraXViewfinder out of the let block, so that it doesn't depend on the existing of surfaceRequest and it renders before the camera, yes, but after I click the button the whole thing disappears, i.e. both the camera view and the row and button composables. I first tried implementing a scaffold as a container and the button was a floating action button, because I thought it would be easier. I tried wrapping the code shown above into another box, to simplify the code, and then I removed the whole wrapping container. I tried to copy as much as possible the code from the second part of the medium tutorial and I see no logical differences between that and my solution. I tried asking ai with no result. I don't know what else to do and I do not understand how to fix this, because I do not understand why exatcly it is happening.

I appreaciate all the help I can get.

Share Improve this question asked Mar 13 at 23:15 AngelAngel 11 bronze badge
Add a comment  | 

1 Answer 1

Reset to default 0

The problem with this code was that in the screen composable I create the viewModel with the class constructor, like so:

@Composable
fun CameraScreen(
    modifier: Modifier = Modifier,
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
    cameraViewModel: CameraViewModel = CameraViewModel(),
)

Which doesn't make it part of the composition, nor does it save its state. To solve this I just changed the assignment of the default value as follows:

@Composable
fun CameraScreen(
    modifier: Modifier = Modifier,
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
    cameraViewModel: CameraViewModel = viewModel()
)

And now everything works correctly!

本文标签: