I faced an issue due to using if condition and a variable state in showing ModalBottomSheet.
In the code below, I want to change user profile image with one of two methods:
- Camera
- Single Photo Picker
then I will send it to function to detect if the image has face or not.
if it has a face then I will send it back to the viewmodel.
the problem is when I click change the image recomposed due to changing of showChooserSheet
, so what can I do to overcome the problem?, Note that I have tried hiding the sheet using scope.launch { state.hide() }
doesn’t work as it freezes the UI and the bottom sheet also still in the screen in the bottom.
Here is my code:
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PhotoSelectorView(
profileImage: String?,
onChangeProfileImage: (File) -> Unit,
) {
val context = LocalContext.current
val file by remember { mutableStateOf(context.createImageFile()) }
val uri by remember {
mutableStateOf(
FileProvider.getUriForFile(
Objects.requireNonNull(context),
BuildConfig.APPLICATION_ID + ".provider", file
)
)
}
val scope = rememberCoroutineScope()
var selectedImageUri by remember {
mutableStateOf<Uri?>(null)
}
var capturedImageUri by remember {
mutableStateOf<Uri>(Uri.EMPTY)
}
val singlePhotoPickerLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.PickVisualMedia(),
onResult = { uriImage -> selectedImageUri = uriImage }
)
val cameraLauncher =
rememberLauncherForActivityResult(
contract = ActivityResultContracts.TakePicture(),
onResult = { isPictureTaken ->
if (isPictureTaken) {
capturedImageUri = uri
}
}
)
val permissionLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
) {
if (it) {
cameraLauncher.launch(uri)
} else {
Toast.makeText(context, R.string.permission_denied, Toast.LENGTH_SHORT).show()
}
}
var showChooserSheet by rememberSaveable { mutableStateOf(false) }
val sheetState = rememberModalBottomSheetState()
if (showChooserSheet) {
UploadPhotoOptionsSheet(
bottomSheetState = sheetState,
onDismissRequest = {
scope.launch {
sheetState.hide()
showChooserSheet = false
}
},
onTakePhoto = {
permissionLauncher.launch(android.Manifest.permission.CAMERA)
showChooserSheet = false
selectedImageUri = null
},
onChooseFromGallery = {
singlePhotoPickerLauncher.launch(
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
)
showChooserSheet = false
capturedImageUri = Uri.EMPTY
},
isDark = isSystemInDarkTheme()
)
}
SubcomposeAsyncImage(
model = ImageRequest
.Builder(context = context)
.data(
if (capturedImageUri != Uri.EMPTY) capturedImageUri
else if (selectedImageUri != null) selectedImageUri
else profileImage?.convertToBitmap()
)
.crossfade(true)
.build(),
contentDescription = null,
contentScale = ContentScale.Crop,
loading = {
CircularProgressIndicator()
},
error = {
Image(
painter = painterResource(id = R.drawable.user_img),
contentDescription = null
)
},
modifier = Modifier
.size(150.dp)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.surface)
)
MyTextButton(
text = R.string.change,
onClick = {
scope.launch {
showChooserSheet = true
sheetState.show()
}
},
isUnderlined = false
)
LaunchedEffect(selectedImageUri, capturedImageUri) {
if (selectedImageUri != null) {
detectFace(context, selectedImageUri!!, { hasFace ->
if (hasFace) {
val tempFile = selectedImageUri!!.createFileFromUri(context)
onChangeProfileImage(tempFile)
} else {
Toast.makeText(context, R.string.face_not_detected, Toast.LENGTH_SHORT).show()
selectedImageUri = null
}
}, {
Toast.makeText(context, it.toString(), Toast.LENGTH_SHORT).show()
selectedImageUri = null
})
}
if (capturedImageUri != Uri.EMPTY) {
detectFace(context, capturedImageUri, { hasFace ->
if (hasFace) {
val tempFile = capturedImageUri.createFileFromUri(context)
onChangeProfileImage(tempFile)
} else {
Toast.makeText(context, R.string.face_not_detected, Toast.LENGTH_SHORT).show()
capturedImageUri = Uri.EMPTY
}
}, {
Toast.makeText(context, it.toString(), Toast.LENGTH_SHORT).show()
capturedImageUri = Uri.EMPTY
})
}
}
}