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