hi everybody, I’ve got a problem with a LazyColum...
# compose
m
hi everybody, I’ve got a problem with a LazyColumn of elements that have a favourite button: basically when I tap the favourite button, the item that is being favourited (a document in my case) is changed in the underlying data structure in the VM, but the view isn’t updated, so I never see any change in the button state 🧵
🧵 2
c
Can you please put your code blocks in the thread? That’s why people are reacting with 🧵
m
of course, sorry!
Copy code
class MainViewModel(private val ioDispatcher: CoroutineDispatcher = <http://Dispatchers.IO|Dispatchers.IO>) : ViewModel() {
    var documentList = emptyList<PDFDocument>().toMutableStateList()
    ....
    fun toggleFavoriteDocument(pdfDocument: PDFDocument) {
        documentList.find {
            it == pdfDocument
        }?.let {
            it.favorite = !it.favorite
        }
    }
}
The composables are:
Copy code
@Composable
fun DocumentRow(
    document: PDFDocument,
    onDocumentClicked: (String, Boolean) -> Unit,
    onFavoriteValueChange: (Uri) -> Unit
) {
HeartIcon(
                isFavorite = document.favorite,
                onValueChanged = { onFavoriteValueChange(document.uri) }
            )
}

@Composable
fun HeartIcon(
    isFavorite: Boolean,
    color: Color = Color(0xffE91E63),
    onValueChanged: (Boolean) -> Unit
) {
    IconToggleButton(
        checked = isFavorite,
        onCheckedChange = {
            onValueChanged()
        }
    ) {
        Icon(
            tint = color,
            imageVector = if (isFavorite) {
                Icons.Filled.Favorite
            } else {
                Icons.Default.FavoriteBorder
            },
            contentDescription = null
        )
    }
}
am I doing something wrong? because when I call the
toggleFavouriteDocument
in the
ViewModel
, I see it’s marked or unmarked as favourite but there is no recomposition at all anywhere (edited)
c
It’s no problem. 👍 Sorry I can’t offer any contribution to your specific problem; good luck getting it resolved.
o
Does your
favorite
property in
PDFDocument
baked by
State
?
m
what do you mean? although I guess if I don’t know what you mean, that means that it is NOT backed by
State
o
Could you show us your
PDFDocument
class?
m
Copy code
@Parcelize 
data class PDFDocument(var uri: String, var favorite: Boolean): Parcelable()
o
Having a mutable properties (var in this case) inside the mutable container (MutableList) is generally a bad idea. Compose simply could not track this changes. It tracks only changes in the
State
properties. So I guess something like this should work:
Copy code
@Parcelize
data class PDFDocument(val uri: String, val initialFavorite: Boolean): Parcelable {
    var favorite by mutableStateOf(initialFavorite)
}
However I would recommend to make your models fully immutable:
Copy code
@Parcelize
data class PDFDocument(val uri: String, val favorite: Boolean): Parcelable
And create a new ones (for example with a
.copy()
) when the state changes:
Copy code
fun toggleFavoriteDocument(pdfDocument: PDFDocument) {
    documentList.replaceAll { if (it == pdfDocument) it.copy(favorite = !it.favorite) else it }
}
a
Use regular class for a mutable class and data class for a immutable class. Do not use data class for a mutable class.
m
thanks! unfortunately it’s not working yet
I have this:
Copy code
fun toggleFavoriteDocument(pdfDocument: PDFDocument) {
        documentList.find {
            it == pdfDocument
        }?.let {
            it.copy(fav = !it.fav)
        }.also {
            _documentListLiveData.postValue(documentList)
        }
    }
and when I set a breakpoint in the last
postValue
to see the content of the
documentList
the documents are never
favorite = true
nor
fav = true
doing this works, but the icon is never changed:
Copy code
fun toggleFavoriteDocument(pdfDocument: PDFDocument) {
        documentList.forEachIndexed { index, document ->
            pdfDocument.takeIf {
                document == pdfDocument
            }?.let {
                documentList[index] = document.copy(fav = !document.favorite)
            }
        }.also {
            _documentListLiveData.postValue(documentList)
        }
    }
perhaps there’s something wrong in the way I hoisted the state in
DocumentRow
and
HeartIcon
?
o
Oh, you are using
LiveData
, in this case something like this should work:
Copy code
fun toggleFavoriteDocument(pdfDocument: PDFDocument) {
    val newDocuments = _documentListLiveData.value?.map {
        if (it == pdfDocument) {
            it.copy(favorite = !it.favorite)
        } else {
            it
        }
    }
    _documentListLiveData.postValue(newDocuments)
}
Why do you have a
documentList
property then?
m
Yes, a mutable list
I have tried however with both LiveData and mutable state list
I’ll make it work, thanks a lot everybody!!