androidandroid-jetpack-composeandroid-jetpacklazycolumncoil

AsyncImage (Coil) Caching not working inside LazyColumn


I am having a composable named ProjectCard. Inside that, I have an AsyncImage composable to retrieve project's icon from Firebase Storage (in case it is supposed to be already uploaded there). In the Search Page (where I want to get details of a certain user), I am using a LazyColumn to show their projects and I was expecting that once I load all images, there's no need to do it anymore but the problem is that... upon scrolling the LazyColumn, the ProjectCard(s) which are getting out of the visible part of screen are trying to load the image back when they get back focus. Moreover, the loading is considerably slow, thus pointing out the fact that it's trying to fetch from my Firebase Storage only and definitely not from any Cache.

ProjectCard.kt :

@Composable
fun ProjectCard(
    project: Project
) {
    val context = LocalContext.current
    var uriOfProjectIconFromFirebase by remember {
        mutableStateOf<Uri?>(null)
    }

    LaunchedEffect(Unit) {
        if (project.storedInFirebase) {
            Log.i(TAG, "Trying to get uri of Project Icon...")
            Firebase.storage.reference.child(
                "projectIcons/${project.belongsTo}/${project.name}.jpg"
            ).downloadUrl.addOnSuccessListener { uri ->
                uriOfProjectIconFromFirebase = uri
            }
        }
    }

    Card(
        modifier = Modifier
            .fillMaxWidth()
    ) {
        Box(
            modifier = Modifier.background(
                Brush.verticalGradient(
                    listOf(
                        project.tintColor,
                        Color.White
                    )
                )
            )
        )
        {
            Column(
                modifier = Modifier.padding(15.dp)
            ) {
                Row(verticalAlignment = Alignment.CenterVertically) {
                    AsyncImage(
                        model =
                        if (project.storedInFirebase) {
                            uriOfProjectIconFromFirebase
                        } else if (project.iconPath != null) {
                            BitmapFactory.decodeFile(project.iconPath)
                        } else {
                            project.iconId
                        },
                        contentDescription = "Project Icon",
                        contentScale = ContentScale.Crop,
                        modifier = Modifier
                            .height(50.dp)
                            .width(50.dp)
                            .clip(RoundedCornerShape(12.dp))
                    )

                    Spacer(modifier = Modifier.width(15.dp))

                    Text(
                        text = project.name,
                        fontSize = 18.sp,
                        fontFamily = FontFamily.SansSerif,
                        fontWeight = FontWeight.SemiBold,
                        color = Color.Black
                    )
                    Spacer(modifier = Modifier.width(10.dp))

                    Image(
                        painter = painterResource(id = R.drawable.link_icon),
                        contentDescription = "",
                        modifier = Modifier.clickable {
                            val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(project.link))
                            context.startActivity(browserIntent)
                        }
                    )
                }

                HorizontalDivider(
                    Modifier.padding(vertical = 5.dp),
                    color = Color.DarkGray
                )

                Text(
                    text = project.description,
                    color = Color.DarkGray
                )
            }
        }

    }
}

The LazyColumn:

LazyColumn(verticalArrangement = Arrangement.spacedBy(5.dp)) {
                        items(projectWrapperOfPersonSearched.projectsList.size)
                        {
                            ProjectCard(project = projectWrapperOfPersonSearched.projectsList[it])
                        }
                    }

I tried using LaunchedEffect but it didn't help either.


Solution

  • I solved it by doing the Uri fetching part in the parent composable itself and then simply passed them as a parameter to the ProjectCard composable. The parent composable is supposed to recompose very rarely so it somehow does the work.

    In the parent composable (SearchPage), when a search is performed:

    projectWrapperOfPersonSearched.projectsList.forEach {
                                    Firebase.storage.reference.child(
                                        "projectIcons/${it.belongsTo}/${it.name}.jpg"
                                    ).downloadUrl.addOnSuccessListener { uri ->
                                        iconUrls[it.name] = uri
                                    }
                                }
    

    The LazyColumn looks like this now:

    LazyColumn(verticalArrangement = Arrangement.spacedBy(5.dp)) {
                            itemsIndexed(projectWrapperOfPersonSearched.projectsList)
                            { _, item ->
                                ProjectCard(
                                    project = item,
                                    iconUrl = iconUrls.getOrDefault(item.name, null)
                                )
                            }
                        }
    

    where iconUrls is defined like this:

    val iconUrls: SnapshotStateMap<String, Uri> = remember {
            mutableStateMapOf()
        }
    

    Finally, the ProjectCard transforms into this:

    @Composable
    fun ProjectCard(
        project: Project,
        iconUrl: Uri? = null
    ) {
        val context = LocalContext.current
    
        Card(
            modifier = Modifier
                .fillMaxWidth()
        ) {
            Box(
                modifier = Modifier.background(
                    Brush.verticalGradient(
                        listOf(
                            project.tintColor,
                            Color.White
                        )
                    )
                )
            )
            {
                Column(
                    modifier = Modifier.padding(15.dp)
                ) {
                    Row(verticalAlignment = Alignment.CenterVertically) {
                        AsyncImage(
                            model =
                            if (project.storedInFirebase && iconUrl!=null) {
                                iconUrl
                            } else if (project.iconPath != null) {
                                BitmapFactory.decodeFile(project.iconPath)
                            } else {
                                project.iconId
                            },
                            imageLoader = context.imageLoader,
                            contentDescription = "Project Icon",
                            contentScale = ContentScale.Crop,
                            modifier = Modifier
                                .height(50.dp)
                                .width(50.dp)
                                .clip(RoundedCornerShape(12.dp))
                        )
    
                        Spacer(modifier = Modifier.width(15.dp))
    
                        Text(
                            text = project.name,
                            fontSize = 18.sp,
                            fontFamily = FontFamily.SansSerif,
                            fontWeight = FontWeight.SemiBold,
                            color = Color.Black
                        )
                        Spacer(modifier = Modifier.width(10.dp))
    
                        Image(
                            painter = painterResource(id = R.drawable.link_icon),
                            contentDescription = "",
                            modifier = Modifier.clickable {
                                val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(project.link))
                                context.startActivity(browserIntent)
                            }
                        )
                    }
    
                    HorizontalDivider(
                        Modifier.padding(vertical = 5.dp),
                        color = Color.DarkGray
                    )
    
                    Text(
                        text = project.description,
                        color = Color.DarkGray
                    )
                }
            }
    
        }
    }