Средство выбора изображений из галереи для создания Jetpack — Android/Kotlin

#android #kotlin #android-jetpack-compose

Вопрос:

Я хочу реализовать средство выбора изображений с помощью Jetpack compose Я искал решения и нашел несколько учебных пособий, подобных этому https://ngengesenior.medium.com/pick-image-from-gallery-in-jetpack-compose-5fa0d0a8ddaf Я использовал код, который они объяснили, и он работал нормально, но у меня проблема!

Мое приложение включает в себя одно действие «Основная активность», которое запускает рендеринг компонентов композиции, один из моих экранов представляет собой форму с полем для выбора изображения и другими полями, когда я использовал приведенный ниже код, открывается галерея, и я выбираю изображение, и при нажатии кнопки » ОК » оно переходит в основную активность, но вместо этого мне нужно оставаться на том же экране формы, чтобы пользователь мог продолжить заполнение формы, я перечислю код и надеюсь, что кто-нибудь сможет мне помочь с этим

 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")
                        }
                    }
                }
            }
        }
    }


}
 

теперь я хочу выбрать изображение из галереи при нажатии на кнопку «Выбрать изображение» и вернуться в свою форму, чтобы заполнить остальные поля. Когда я попробовал приведенный выше код, я мог выбрать изображение, но оно переходит к основному действию, из-за которого я потерял данные

есть ли помощь в решении этой проблемы?

Ответ №1:

Когда вы говорите «это сработало нормально», вы имеете в виду, что вы проверили, что «val imageUri» имеет правильный Uri выбранного файла ?

Вы получаете какие-либо ошибки от logcat после выбора изображения из средства выбора изображений ?

Попробуйте с этим и дайте нам знать, если это что-нибудь даст

 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
}
 

Комментарии:

1. проблема в том, что после выбора изображения оно переходит к основному действию, а не остается на экране формы! Я только что протестировал его и дал мне правильный Uri, и даже он просто отобразил изображение, но перешел к основному действию, поэтому мне нужно открыть экран формы с самого начала, мне нужно что-то, что удерживает его на том же экране после выбора изображения, вы понимаете мою проблему?

2. Я вас понял, и мы можем посмотреть, как настроен ваш навигационный контроллер ?

3. это просто основное, как описано в документах, и вы можете увидеть некоторый код из основного действия и основной навигации по адресу: gist.github.com/mstva/a391b2e1c57a2e02d8cbeecd3c2d3e9b дайте мне знать, если вы хотите узнать что-то еще

Ответ №2:

Действительно, кажется, что это не имеет отношения к тому, как вы используете лаунчер. Я понимаю, что проблема в том, что экран возвращается к основному действию после того, как вы выберете файл изображения из средства выбора изображений, на этом этапе я бы проверил, как управляется backStackEntry. Попробуйте прокомментировать тело «составить» из MainActivity и понаблюдайте, не приведет ли это к чему-нибудь. —> прокомментируйте этот блок.

 {backStackEntry -> backStackEntry.arguments?.getString("form)?.let { _form ->
            val form = Gson().fromJson(_form, FormModel::class.java)
            FormDetailScreen(
                navController = navController,
                form = form,
                formViewModel = formViewModel
            )
        }
    }
 

Ответ №3:

Я был в той же ситуации, что и вы, если бы хорошо понимал вашу проблему. Вот как я это сделал. Это немного банально, но это работает.

У меня есть это

 //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)
        }
    }
}
 

Класс для обработки разрешений в приложении

 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)
    }
}
 

Затем в основной активности, когда мы получаем изображение, мы запускаем вызов с помощью шины событий

 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)
                    )
                }
            }
        }
    }
  }
}
 

Возможно, есть лучшее решение

Возможно, стоит проверить библиотеку Jetpack Compose для аккомпаниатора, на данный момент она все еще экспериментальная https://github.com/google/accompanist/tree/main/permissions и вот этот док : https://google.github.io/accompanist/permissions/