i've got a list of bitmaps wrapped in a mutableLiveData in my viewModel, and i'm pulling bitmaps from an online database and adding them to the list as they arrive. for some reason, it isn't triggering a recomposition when i update the list. i even tried calling 'postvalue' on the list, but it still didn't work.
package com.example.firebasetests
import android.content.ContentValues.TAG
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.firebasetests.ui.theme.FirebasetestsTheme
import com.google.firebase.firestore.ktx.firestore
import com.google.firebase.ktx.Firebase
import androidx.compose.runtime.livedata.observeAsState
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.firebase.ui.auth.AuthUI
import com.firebase.ui.auth.FirebaseAuthUIActivityResultContract
import com.firebase.ui.auth.data.model.FirebaseAuthUIAuthenticationResult
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.actionCodeSettings
import com.google.firebase.auth.ktx.auth
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.storage.StorageReference
import com.google.firebase.storage.ktx.storage
import java.io.ByteArrayOutputStream
import com.google.firebase.storage.component1
import com.google.firebase.storage.component2
import androidx.compose.runtime.*
import androidx.compose.foundation.lazy.items
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import kotlinx.coroutines.flow.MutableStateFlow
class MainActivity : ComponentActivity() {
private lateinit var auth: FirebaseAuth
// See: https://developer.android.com/training/basics/intents/result
private val signInLauncher = registerForActivityResult(
FirebaseAuthUIActivityResultContract(),
) { res ->
this.onSignInResult(res)
}
// Choose authentication providers
val providers = arrayListOf(
AuthUI.IdpConfig.EmailBuilder().build(),
AuthUI.IdpConfig.PhoneBuilder().build(),
AuthUI.IdpConfig.GoogleBuilder().build(),
// AuthUI.IdpConfig.E
)
private fun onSignInResult(result: FirebaseAuthUIAuthenticationResult) {
val response = result.idpResponse
if (result.resultCode == RESULT_OK) {
// Successfully signed in
val user = FirebaseAuth.getInstance().currentUser
// ...
} else {
// Sign in failed. If response is null the user canceled the
// sign-in flow using the back button. Otherwise check
// response.getError().getErrorCode() and handle the error.
// ...
}
}
val viewModel = FeedUIViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
FirebasetestsTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
val db = Firebase.firestore
auth = Firebase.auth
MyApp(db, viewModel)
val storage = Firebase.storage
var storageRef = storage.reference
var imageRef: StorageReference = storageRef.child("baloo.png")
val pepeRef = storageRef.child("pepe.jpg")
val pepe = BitmapFactory.decodeResource(resources,R.raw.pepe)
val baos = ByteArrayOutputStream()
pepe.compress(Bitmap.CompressFormat.JPEG, 100, baos)
val data = baos.toByteArray()
var uploadTask = pepeRef.putBytes(data)
uploadTask.addOnFailureListener{
}.addOnSuccessListener {
}
val ONE_MEGABYTE: Long = 1024 * 1024
storageRef.listAll()
.addOnSuccessListener { (items, prefixes) ->
for (prefix in prefixes) {
// All the prefixes under listRef.
// You may call listAll() recursively on them.
}
for (item in items) {
// All the items under listRef.
val itemRef = storageRef.child(item.name)
itemRef.getBytes(ONE_MEGABYTE).addOnSuccessListener {imageBytes ->
// Convert byte array to bitmap
val bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
viewModel.profileImages.value?.add(bitmap)
viewModel.profileImages.postValue(viewModel.profileImages.value)
//Log.d("MYTAG",""+viewModel.profileImages.value?.size)
}
}
}
.addOnFailureListener {
// Uh-oh, an error occurred!
}
imageRef.getBytes(ONE_MEGABYTE).addOnSuccessListener {imageBytes ->
// Convert byte array to bitmap
val bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
//viewModel.image.value = bitmap
// Data for "images/island.jpg" is returned, use this as needed
Log.d("MYTAG","got image")
}.addOnFailureListener {
// Handle any errors
}
// Create and launch sign-in intent
/*
val signInIntent = AuthUI.getInstance()
.createSignInIntentBuilder()
.setAvailableProviders(providers)
.build()
signInLauncher.launch(signInIntent)
*/
}
}
}
}
}
class FeedUIViewModel: ViewModel(){
var image: MutableLiveData<Bitmap> = MutableLiveData()
var profileImages: MutableLiveData<MutableList<Bitmap>> = MutableLiveData(mutableListOf<Bitmap>())
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MyApp(db: FirebaseFirestore, viewModel: FeedUIViewModel) {
var text by remember { mutableStateOf("") }
var clickedText by remember{ mutableStateOf("") }
val emptyBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
emptyBitmap.eraseColor(Color.BLUE)
val image by viewModel.image.observeAsState(emptyBitmap)
val profiles by viewModel.profileImages.observeAsState()
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
// Text Entry Field
TextField(
value = text,
onValueChange = {
text = it
},
label = { Text("Label") }
)
// Button to the right of the text entry field
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = Arrangement.End
) {
Button(
onClick = {
clickedText = text
// Create a new user with a first and last name
val string = hashMapOf(
"string" to text,
)
// Add a new document with a generated ID
db.collection("users").document("chat1")
.set(string)
.addOnSuccessListener { documentReference ->
Log.d("BIGTAG", "DocumentSnapshot added with ID: $documentReference")
}
.addOnFailureListener { e ->
Log.w("BIGTAG", "Error adding document", e)
}
},
modifier = Modifier
.wrapContentWidth()
) {
Text("Display Text")
}
}
// Text Display Field
Text(
text = clickedText,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
)
LazyColumn{
items(profiles!!){ profilePic->
Image(
bitmap = profilePic.asImageBitmap(),
contentDescription = null, // Content description for accessibility
modifier = Modifier
.fillMaxWidth()
.height(200.dp) // Adjust the height as needed
.padding(16.dp)
)
}
}
/*
Image(
painter = BitmapPainter(image.asImageBitmap()),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.size(200.dp) // Adjust the size as needed
)
*/
}
}
just found the solution, i wasn't updating mutableLiveData properly: