mutableLiveData doesn't trigger recomposition when adding items to a list

35 views Asked by At

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
        )
*/



    }
}
1

There are 1 answers

0
Russell Butler On

just found the solution, i wasn't updating mutableLiveData properly:

viewModel.profileImages.value = (viewModel.profileImages.value ?: mutableListOf()).toMutableList().apply {
    add(bitmap)
}