Reputation: 171
I want to implement an image picker using Jetpack compose I was searching for solutions and I found some tutorials like this one https://ngengesenior.medium.com/pick-image-from-gallery-in-jetpack-compose-5fa0d0a8ddaf I used the code they explained and it worked fine but I have a problem!
My app includes one activity "MainActivity" which start rendering compose components, one of my screens is a form with a field to select image, and other fields, when I used the code below it opens the gallery and I select an image and when clicking OK, it goes to the MainActivity, but I need instead to stay in the same screen of the form so user could continue completing the form, I will list the code and I hope someone could help me with this
val launcher = rememberLauncherForActivityResult(contract =
ActivityResultContracts.GetContent()) { uri: Uri? ->
imageUri = uri
}
Column() {
Button(onClick = {
launcher.launch("image/*")
}) {
Text(text = "Pick image")
}
Spacer(modifier = Modifier.height(12.dp))
imageUri?.let {
if (Build.VERSION.SDK_INT < 28) {
bitmap.value = MediaStore.Images
.Media.getBitmap(context.contentResolver,it)
} else {
val source = ImageDecoder
.createSource(context.contentResolver,it)
bitmap.value = ImageDecoder.decodeBitmap(source)
}
bitmap.value?.let { btm ->
Image(bitmap = btm.asImageBitmap(),
contentDescription =null,
modifier = Modifier.size(400.dp))
}
}
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Button(
onClick = {
navController.navigate(AppScreens.FormScreen.route)
},
) {
Text(text = "Go to form screen" )
}
}
}
}
@Composable
fun FormScreen() {
var imageUri by remember { mutableStateOf<Uri?>(null) }
val context = LocalContext.current
var bitmap by remember { mutableStateOf<Bitmap?>(null) }
val launcher = rememberLauncherForActivityResult(contract =
ActivityResultContracts.GetContent()) { uri: Uri? ->
imageUri = uri
}
Column {
// some text field in the form
// another number field in the form
// select image filed in the form
CustomInputFieldContainer(
label = "select image"
) {
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
imageUri?.let {
if (Build.VERSION.SDK_INT < 28) {
bitmap = MediaStore.Images
.Media.getBitmap(context.contentResolver,it)
} else {
val source = ImageDecoder
.createSource(context.contentResolver,it)
bitmap = ImageDecoder.decodeBitmap(source)
}
bitmap.let { btm ->
Image(bitmap = btm.asImageBitmap(),
contentDescription =null,
modifier = Modifier.size(400.dp))
}
}
Button(
onClick = { launcher.launch("image/*") },
contentPadding = PaddingValues(),
modifier = Modifier.background(Color.Yellow)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentSize(Alignment.BottomCenter)
.padding(vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(imageVector = Icons.Filled.AddAPhoto, contentDescription = null)
Spacer(modifier = Modifier.width(8.dp))
Text(text = "Add Photo")
}
}
}
}
}
}
}
now I want to select an image from the gallery when clicking on select image button and back to my form to complete the rest of the fields, when I tried the above code I could select an image but it goes to the main activity which makes me lost the data
any help for solving this problem?
Upvotes: 4
Views: 7980
Reputation: 43
I was in the same situation than you if I understood well your problem. Here's how I did it. It's a bit hacky but it works.
I have this
//The clickable composable
ProfilePictureView(
avatarUrl, firstname, lastname, isUpdatingProfileImage,
Modifier
.padding(top = 50.dp)
.size(110.dp)
.align(Alignment.CenterHorizontally),
onClick = {
scope.launch {
openImagePicker(permissionManager, LocalContext.current as MainActivity)
}
})
fun openImagePicker(permissionManager: PermissionManager, activity: AppCompatActivity) {
permissionManager.requestStoragePermission {
if (it) {
val intent = Intent(Intent.ACTION_PICK)
intent.type = "image/*"
startActivityForResult(activity, intent, MainActivity.IMAGE_PICK_CODE, null)
}
}
}
A class to handle permissions in the app
class PermissionManager(private val activity: AppCompatActivity) {
private val errorHandler = CoroutineExceptionHandler { _, throwable ->
Timber.e("$throwable")
}
private val scope = CoroutineScope(Job() + Dispatchers.IO + errorHandler)
fun requestStoragePermission(onResult: (Boolean) -> Unit) {
requestPermission(
Manifest.permission.READ_EXTERNAL_STORAGE,
Permissions.STORAGE.code,
onResult
)
}
fun requestPermission(permission: String, permissionCode: Int, onResult: (Boolean) -> Unit) {
if (
ContextCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_DENIED
) {
val permissions = arrayOf(permission)
scope.launch {
activity.requestPermissions(permissions, permissionCode)
LiveEventBus.listen<EventPermissionResult>().collect {
onResult.invoke(it.isGranted)
}
}
} else {
onResult.invoke(true)
}
}
enum class Permissions(val code: Int) {
STORAGE(1001)
}
}
Then in the MainActivity when we get the image, we trigger the call with the eventBus
class MainActivity : AppCompatActivity() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK) {
lifecycleScope.launch {
when (requestCode) {
IMAGE_PICK_CODE -> data?.data?.let {
LiveEventBus.send(
EventImageSelected(it)
)
}
}
}
}
}
}
There's probably a better solution
It might be worth checking the Jetpack Compose Accompanist lib for that It's still experimental at the moment https://github.com/google/accompanist/tree/main/permissions and the doc here : https://google.github.io/accompanist/permissions/
Upvotes: 0
Reputation: 594
Indeed it doesn't seem it has to do with how you're using the launcher. I understand the issue is that the screen comes back to the main activity after you select your image file from the image picker, at this point I would check how the backStackEntry is being managed. Try to comment out the "compose" body from mainActivity and observe if that does anything. ---> comment out this block.
{backStackEntry -> backStackEntry.arguments?.getString("form)?.let { _form ->
val form = Gson().fromJson(_form, FormModel::class.java)
FormDetailScreen(
navController = navController,
form = form,
formViewModel = formViewModel
)
}
}
Upvotes: 0
Reputation: 594
When you say "it worked fine" do you mean you checked that " val imageUri'" have the correct Uri of the file picked ?
Do you get any error from the logcat after selecting an image from the Image-Picker ?
Try with this and let us know if it does anything
var imageUri = remember { mutableStateOf<Uri?>(null) } // UPDATE
val context = LocalContext.current
var bitmap by remember { mutableStateOf<Bitmap?>(null) }
val launcher = rememberLauncherForActivityResult(contract =
ActivityResultContracts.GetContent()) { uri: Uri? ->
imageUri.value = uri // UPDATE
}
Upvotes: 1