How to correctly upload image to an item in LazyList in Jetpack Compose?

How to correctly upload image to an item in LazyList in Jetpack Compose?

Problem Description:

I would like to let user add image to each item (Card) in LazyColumn. But it seems that images are getting deleted on recomposition. How can I fix that?

@Composable
fun PhotoUpload(
) {
    val imageUri = remember {
        mutableStateOf<Uri?>(null)
    }
    val context = LocalContext.current
    val bitmap = remember {
        mutableStateOf<Bitmap?>(null)
    }

    val launcher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.GetContent()
    ) { uri: Uri? ->
        imageUri.value = uri
    }

    imageUri.value?.let {
        LaunchedEffect(Unit) {
            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)
        )
    }

    Button(onClick = {
        launcher.launch("image/*")
    }) {
        Icon(Icons.Filled.PhotoAlbum, "")
    }
}

example

Images get deleted (they’re not coming back, it’s a loop gif)

PS: For LazyColumn I do use keys. And I also tried using AsyncImage from Coil, but it had the same issue

Solution – 1

It’s getting removed because when you scroll away from that uploaded image, the LazyColumn remove the items that are not longer visible for better performance, LazyList uses SubcomposeLayout under the hood which recomposes visible items and one that about to be visible in scroll direction, which is not possible cause you didn’t save them somewhere out of the recomposition (e.g view model)!

You can store Uris or Uris as String inside a data class such as

@Immutable
data class MyModel(
    val title: String,
    val description: String,
    val uri: Uri? = null
)

And create a ViewModel with items and a function to update Uri when it’s set

class MyViewModel : ViewModel() {
    val items = mutableStateListOf<MyModel>()
        .apply {
            repeat(15) {
                add(MyModel(title = "Title$it", description = "Description$it"))
            }
        }

    fun update(index: Int, uri: Uri) {
        val item = items[index].copy(uri = uri)
        items[index] = item
    }
}

And getting Uri via clicking a Button

@Composable
fun PhotoUpload(
    onError: (() -> Unit)? = null,
    onImageSelected: (Uri) -> Unit
) {

    val launcher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.GetContent()
    ) { uri: Uri? ->
        if (uri == null) {
            onError?.invoke()
        } else {
            onImageSelected(uri)
        }

    }

    Button(onClick = {
        launcher.launch("image/*")
    }) {
        Icon(Icons.Filled.PhotoAlbum, "")
    }
}

Row that returns Uri via callback when it’s set

@Composable
private fun MyRow(
    title: String,
    description: String,
    uri: Uri?,
    onImageSelected: (Uri) -> Unit
) {
    Column(
        modifier = Modifier
            .shadow(ambientColor = Color.LightGray, elevation = 4.dp)
            .fillMaxWidth()
            .background(Color.White, RoundedCornerShape(8.dp))
            .padding(8.dp)
    ) {
        Text(title)
        Text(description)
        uri?.let { imageUri ->
            AsyncImage(
                modifier = Modifier
                    .fillMaxWidth()
                    .aspectRatio(4 / 3f),
                model = imageUri, contentDescription = null
            )
        }
        PhotoUpload(onImageSelected = onImageSelected)
    }
}

Usage

@Composable
private fun ImageUploadSample(viewModel: MyViewModel) {
    LazyColumn {

        itemsIndexed(
            key = { _, item ->
                item.hashCode()
            },
            items = viewModel.items
        ) { index, myModel ->
            MyRow(
                title = myModel.title,
                description = myModel.description,
                uri = myModel.uri,
                onImageSelected = { uri ->
                    viewModel.update(index, uri)
                }
            )
        }
    }
}

Result

enter image description here

Rate this post
We use cookies in order to give you the best possible experience on our website. By continuing to use this site, you agree to our use of cookies.
Accept
Reject