Home > Back-end >  Fetching photos from Firebase Cloud Storage causes flickering on Android Jetpack Compose
Fetching photos from Firebase Cloud Storage causes flickering on Android Jetpack Compose

Time:11-12

I'm developing an Android app using Kotlin. I need to display pictures from Cloud Storage on the screens. Now, pictures are displayed but they flickers. I can't find infomation written in Kotlin and I have no idea why this is happening. This is my current code.

@Composable
fun UserInfo(navController: NavController, name: String, uid: String) {
    val storage = Firebase.storage
    val userRef = storage
        .reference
        .child("users/${uid}/photos")
        .child(name)
    var bitmap by remember { mutableStateOf<Bitmap?>(null) }
    val ONE_MEGABYTE: Long = 1024 * 1024
    userRef.getBytes(ONE_MEGABYTE).addOnSuccessListener {
        bitmap = BitmapFactory.decodeByteArray(it, 0, it.size)
    }
    ...

            if (userRef != null) {
                Image(
                    painter = rememberImagePainter(bitmap),
                    contentScale = ContentScale.FillBounds,
                    contentDescription = null,
                    modifier = Modifier
                        .width(100.dp)
                        .height(100.dp)
                        .clip(CircleShape)
                )
            ...

Could someone help me? Thank you.

I've moved the codes to Storage Class like this.

class Storage {
    val storage = Firebase.storage

    @Composable
    fun GetPhoto(uid: String, name: String): Bitmap? {
        val userRef = storage.reference
            .child("users/${uid}/photos")
            .child(name)
        var bitmap by remember { mutableStateOf<Bitmap?>(null) }
        val ONE_MEGABYTE: Long = 1024 * 1024
        userRef.getBytes(ONE_MEGABYTE).addOnSuccessListener {
            bitmap = BitmapFactory.decodeByteArray(it, 0, it.size)
        }
        return bitmap
        }
}

And this is UserInfo View now.

@Composable
fun UserInfo(navController: NavController, name: String, uid: String) {
...

 storage.GetPhoto(uid, name)
            val bitmap =
                Image(
                    painter = rememberImagePainter(storage.GetPhoto(uid, name)),
                    contentScale = ContentScale.FillBounds,
                    ...

This is my whole code of UserInfo View.

@Composable
fun UserInfo(navController: NavController, name: String, uid: String) {
val storage = Storage()
Column(
        modifier = Modifier
            .background(Beige)
            .fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .weight(1f),
        horizontalArrangement = Arrangement.Center
    )
    {          
            storage.GetPhoto(uid, name)         
            Image(
               painter = rememberImagePainter(storage.GetPhoto(uid, name)),
               contentScale = ContentScale.FillBounds,
               contentDescription = null,
               modifier = Modifier
                   .width(100.dp)
                   .height(100.dp)
                   .clip(CircleShape)
             )
        }
        Row(
             modifier = Modifier
                 .fillMaxWidth()
                 .weight(1f),
             horizontalArrangement = Arrangement.Center
        ) 
        {
             Button(modifier = Modifier.wrapContentWidth(),
                        colors = ButtonDefaults.textButtonColors(
                            backgroundColor = Pink,
                            contentColor = Brown,
                            disabledContentColor = Color.LightGray
                        ),
                        onClick = {
                        navController.navigate("PostHistory")
                        }
             ) {
                    Text(text = "Posts", fontSize = 20.sp)
               }
            }

CodePudding user response:

The issue is in here, it seems like every invocation of a Firebase storage function listener triggers something that makes it listen for a new return, which in turn updates your bitmapstate everytime and because its a stream of bytes, the value it sets seems always different from the previous and that's enough for compose to trigger a re-composition in this case.

userRef.getBytes(ONE_MEGABYTE).addOnSuccessListener {
     bitmap = BitmapFactory.decodeByteArray(it, 0, it.size)
}

What we can do is wrap all firebase components inside a LaunchedEffect using a key that won't change, preventing it from re-executing on succeeding re-compositions, in this case, once your bitmap state receives a new value from the callback which will trigger a compose update of UserInfo, the LaunchedEffect will not re-execute.

@Composable
fun UserInfo(navController: NavController, name: String, uid: String) {

    var bitmap by remember { mutableStateOf<Bitmap?>(null) }

    LaunchedEffect(Unit) {

        val storage = Firebase.storage
        val userRef = storage
            .reference
            .child("users/${uid}/photos")
            .child(name)

        val ONE_MEGABYTE: Long = 1024 * 1024

        userRef.getBytes(ONE_MEGABYTE).addOnSuccessListener {
            bitmap = BitmapFactory.decodeByteArray(it, 0, it.size)
        }
    }

    if (bitmap != null) {
        Image(
            painter = rememberImagePainter(bitmap),
            contentScale = ContentScale.FillBounds,
            contentDescription = null,
            modifier = Modifier
                .width(100.dp)
                .height(100.dp)
                .clip(CircleShape)
        )
    }
}
  • Related